ログの型定義

ここではログの型について説明します。型の表記は TypeScript で記述します。

ウェブフックの型定義 も利用しています。

基本的な型

// @ts-check
// JSON 値を表します。
// 仕様は RFC 8259 に従います。
type JSONValue =
  | null
  | boolean
  | number
  | string
  | JSONValue[]
  | { [key: string]: JSONValue | undefined };

// UnixTime
// 例: 1704067199
type UnixTime = number;

// RFC3339 UTC (マイクロ秒)
// 例: 2023-12-31T23:59:59.999999Z
type Timestamp = string;

// ストリームの種別
type Role = "sendrecv" | "sendonly" | "recvonly";

// 最大 255 バイト
type ChannelId = string;
type SessionId = string;
type ClientId = string;
type BundleId = string;
// Base32(UUIDv4) 26 文字の文字列
type ConnectionId = string;

// サイマルキャストで視聴する映像の種類
type SimulcastRid = "none" | "r0" | "r1" | "r2";

type OpusParams = {
  channels?: number;
  maxplaybackrate?: number;
  // 6000..510000
  maxaveragebitrate?: number;
  minptime?: number;
  ptime?: number;
  stereo?: boolean;
  sprop_stereo?: boolean;
  useinbandfec?: boolean;
  usedtx?: boolean;
};

// 音声の設定
type Audio =
  | boolean
  | {
      codec_type?: AudioCodecType;
      bit_rate?: number;
      opus_params?: OpusParams;
    };

type VideoVP9Params = {
  // 0..3
  profile_id?: number;
};

type VideoAV1Params = {
  // 0..2
  profile?: number;
  // 0..15
  level_idx?: number;
  // 0..1
  tier?: number;
};

type VideoH264Params = {
  profile_level_id?: string;
  // 0..2
  packetization_mode?: number;
  // 0..1
  level_asymmetry_allowed?: number;
  // sora.conf で h264_b_frame = true を設定する必要があります
  b_frame?: boolean;
};

type VideoH265Params = {
  level_id?: number;
  // 0..31
  profile_id?: number;
  // 0..1
  tier_flag?: number;
  // "SRST" | "MRST" | "MRMT"
  tx_mode?: H265TxMode;
  // sora.conf で h265_b_frame = true を設定する必要があります
  b_frame?: boolean;
};

// 映像の設定
type Video =
  | boolean
  | {
      codec_type?: VideoCodecType;
      bit_rate?: number;
      // 利用するには sora.conf で signaling_vp9_params = true を設定する必要があります
      vp9_params?: VideoVP9Params;
      // 利用するには sora.conf で signaling_av1_params = true を設定する必要があります
      av1_params?: VideoAV1Params;
      // 利用するには sora.conf で signaling_h264_params = true を設定する必要があります
      h264_params?: VideoH264Params;
      // 利用するには sora.conf で signaling_h265_params = true を設定する必要があります
      h265_params?: VideoH265Params;
    };

// 音声コーデックの種類
type AudioCodecType = "OPUS";

// 映像コーデックの種類
type VideoCodecType = "VP9" | "VP8" | "AV1" | "H264" | "H265";

type H265TxMode = "SRST" | "MRST" | "MRMT";

// DataChannel の方向
type Direction = "sendrecv" | "sendonly" | "recvonly";

type DataChannelMessagingHeaderFieldType = "sender_connection_id";

type DataChannelMessagingHeaderField = {
  type: DataChannelMessagingHeaderFieldType;
  // * length は "type": "offer" 時の data_channels にのみ含まれる
  // * length は"type": "connect" 時には指定できない
  // * length は認証成功時の払い出し時には指定できない
  length?: number;
};

// DataChannels
type DataChannel = {
  label: string;
  direction: Direction;
  ordered: boolean;
  max_packet_life_time?: number;
  max_retransmits?: number;
  protocol?: string;
  compress: boolean;
  header?: DataChannelMessagingHeaderField[];
};

type TurnTransportType = "udp" | "tcp";

type ForwardingFilterRuleField = "connection_id" | "client_id" | "kind";

type ForwardingFilterRuleOperator = "is_in" | "is_not_in";

type ForwardingFilterRuleKindValue = "audio" | "video";

type ForwardingFilterRule = {
  field: ForwardingFilterRuleField;
  operator: ForwardingFilterRuleOperator;
  values: string[];
};

type ForwardingFilterAction = "block" | "allow";

type ForwardingFilter = {
  version?: string;
  metadata?: JSONValue;
  name?: string;
  priority?: number;
  action?: ForwardingFilterAction;
  rules: ForwardingFilterRule[][];
};

type SoraClientType =
  | "Sora JavaScript SDK"
  | "Sora iOS SDK"
  | "Sora Android SDK"
  | "Sora Unity SDK"
  | "Sora C++ SDK"
  | "Sora Python SDK"
  | "Sora C SDK"
  | "Sora Flutter SDK"
  | "OBS-Studio"
  | "OBS-Studio-WHIP"
  | "OBS-Studio-WHEP"
  | "WebRTC Native Client Momo"
  | "WebRTC Load Testing Tool Zakuro";

// SoraClient
type SoraClient = {
  environment?: string;
  raw?: string;
  type?: SoraClientType;
  version?: string;
  commit_short?: string;
  libwebrtc?: string;
};

// RTCRtpCodecParameters
// https://www.w3.org/TR/webrtc/#dom-rtcrtpcodecparameters
type SimulcastCodec = {
  // payloadType や channels は省略
  mimeType: string;
  clockRate: number;
  sdpFmtpLine?: string;
};

// RTCRtpEncodingParameters
// https://w3c.github.io/webrtc-pc/#dom-rtcrtpencodingparameters
type SimulcastEncoding = {
  // https://www.w3.org/TR/webrtc/#dom-rtcrtpcodingparameters-rid
  rid: SimulcastRid;
  // https://www.w3.org/TR/webrtc/#dom-rtcrtpencodingparameters-active
  active?: boolean;
  // https://www.w3.org/TR/webrtc/#dom-rtcrtpencodingparameters-maxframerate
  maxFramerate?: number;
  // https://www.w3.org/TR/webrtc/#dom-rtcrtpencodingparameters-maxbitrate
  maxBitrate?: number;
  // https://www.w3.org/TR/webrtc/#dom-rtcrtpencodingparameters-scaleresolutiondownby
  scaleResolutionDownBy?: number;
  // https://www.w3.org/TR/webrtc/#dom-rtcrtpcodec
  codec?: SimulcastCodec;
  // https://w3c.github.io/webrtc-extensions/#dom-rtcrtpencodingparameters-scaleresolutiondownto
  // @ts-expect-error RTCResolutionRestriction は typed で未定義のため型チェックを無視(型定義が追加され次第削除)
  scaleResolutionDownTo?: RTCResolutionRestriction;
  // https://w3c.github.io/webrtc-extensions/#dom-rtcrtpencodingparameters-adaptiveptime
  adaptivePtime?: boolean;
  // https://www.w3.org/TR/webrtc-svc/#dom-rtcrtpencodingparameters-scalabilitymode
  scalabilityMode?: string;
};

型定義

ウェブフックログ

// @ts-check
// Base32 化した UUIDv4
type WebhookID = string;

// ウェブフック URL で HTTP または HTTPS
type WebhookURL = string;

// WebSocket シグナリング HTTP ヘッダーのコピー
type CopyHeaders = Record<string, string>;

// 認証ウェブフックログ (auth_webhook.jsonl)
type AuthWebhookLog = {
  id: WebhookID;
  timestamp: Timestamp;

  req: AuthWebhookLogRequest;
  res: AuthWebhookResponse;

  // ウェブフック URL
  url?: WebhookURL;

  // ウェブフック応答時間 (ミリ秒)
  // auth_webhook_url が未指定の場合はウェブフックを送信しないためキー自体を含めない
  response_time_ms?: number;

  copy_headers?: CopyHeaders;
};

// 認証ウェブフックエラーログ (auth_webhook_error.jsonl)
type AuthWebhookErrorLog = {
  id: WebhookID;
  timestamp: Timestamp;

  req: AuthWebhookLogRequest;

  // ウェブフック URL
  url: WebhookURL;

  // エラー理由
  reason: string;

  // ウェブフック応答時間 (ミリ秒)
  response_time_ms?: number;

  copy_headers?: CopyHeaders;
};

// 認証ウェブフックログ req 項目
type AuthWebhookLogRequest = AuthWebhookRequest & {
  x_forwarded_for?: string[];
};

// 認証ウェブフックログ res 項目
type AuthWebhookResponse = AuthWebhookAcceptResponse | AuthWebhookRejectResponse;

// セッションウェブフックログ (session_webhook.jsonl)
type SessionWebhookLog = {
  id: WebhookID;
  timestamp: Timestamp;

  req: SessionWebhookRequest;

  res?: SessionWebhookResponse;

  // ウェブフック URL
  url?: WebhookURL;

  // ウェブフック応答時間 (ミリ秒)
  // session_webhook_url が未指定、 もしくは ignore_*_webhook で無視するように指定したウェブフックの場合はウェブフックを送信しないためキー自体を含めない
  response_time_ms?: number;
};

// セッションウェブフックエラーログ (session_webhook_error.jsonl)
type SessionWebhookErrorLog = {
  id: WebhookID;
  timestamp: Timestamp;

  req: SessionWebhookRequest;

  // ウェブフック URL
  url?: WebhookURL;

  // エラー理由
  reason: string;

  // ウェブフック応答時間 (ミリ秒)
  response_time_ms?: number;
};

// イベントウェブフックログ (event_webhook.jsonl)
type EventWebhookLog = EventWebhookRequest;

// イベントウェブフックエラーログ (event_webhook_error.jsonl)
type EventWebhookErrorLog = EventWebhookRequest;

// 統計ウェブフックログ (stats_webhook.jsonl)
type StatsWebhookLog = StatsWebhookConnectionRtcRequest;

// 統計ウェブフックエラーログ (stats_webhook_error.jsonl)
type StatsWebhookErrorLog = StatsWebhookConnectionRtcRequest;

コネクションログ

// @ts-check

// コネクションログ (connection.jsonl)
type ConnectionLog = {
  id: WebhookID;
  timestamp: Timestamp;

  node_name: string;
  label: string;
  version: string;

  created_timestamp: Timestamp;
  destroyed_timestamp: Timestamp;

  data_channel_signaling: boolean;
  ignore_disconnect_websocket: boolean;
  ignore_max_connections: boolean;

  role: Role;

  simulcast: boolean;
  simulcast_multicodec: boolean;
  // role が sendrecv または sendonly、simulcast が true、spotlight が false の場合のみ含まれます
  simulcast_encodings?: SimulcastEncoding[];
  simulcast_codecs?: ConnectionLogSimulcastCodec[];

  spotlight: boolean;
  // role が sendrecv または sendonly、simulcast が true、spotlight が true の場合のみ含まれます
  spotlight_encodings?: SimulcastEncoding[];

  channel_id: ChannelId;
  group_id: SessionId;
  session_id: SessionId;
  client_id: ClientId;
  bundle_id: BundleId;
  connection_id: ConnectionId;

  audio: boolean;
  // audio が true の場合のみ含まれます
  audio_codec_type?: AudioCodecType;
  audio_bit_rate?: number;

  video: boolean;
  // video が true の場合のみ含まれます
  video_codec_type?: VideoCodecType;
  video_bit_rate?: number;
  video_width?: number;
  video_height?: number;
  video_vp9_params?: VideoVP9Params;
  video_av1_params?: VideoAV1Params;
  video_h264_params?: VideoH264Params;
  video_h265_params?: VideoH265Params;

  recording_block: boolean;

  turn_transport_type: TurnTransportType;
  turn_transport_src_ip_address?: string;
  turn_transport_src_port?: number;

  sora_client: SoraClient;

  copy_headers: CopyHeaders;

  local_stats: ConnectionLogLocalStats;

  destroyed_reason: ConnectionLogDestroyedReason;
  disconnect_api_reason?: JSONValue;
  type_disconnect_reason?: JSONValue;
  shutdown_reason?: string;
  websocket_terminated_reason?: string;
  data_channel_exit_reason?: string;
  signaling_terminate_reason?: string;

  rpc_methods: string[];

  // クライアント側 RTC 統計情報
  rtc_stats?: RTCStatsReport[];
};

type ConnectionLogDestroyedReason =
  | "normal"
  | "disconnected_api"
  | "session_destroyed"
  | "lifetime_expired"
  | "duplicate_client_id"
  | "abort"
  | "signaling_re_answer_timeout";

type ConnectionLogSimulcastCodec = {
  rid?: SimulcastRid;
  codec_type?: VideoCodecType;
  codec_params?: VideoVP9Params | VideoAV1Params | VideoH264Params | VideoH265Params;
};

type ConnectionLogLocalStats = {
  rtp: ConnectionLogRtpStats;
  rtp_hdrext: ConnectionLogStatsCounters;
  rtcp: ConnectionLogStatsCounters;
  signaling: ConnectionLogSignalingStats;
  simulcast: Partial<Record<SimulcastRid, ConnectionLogSimulcastRidStats>>;
  sctp: ConnectionLogStatsCounters;
  data_channel: Record<string, ConnectionLogDataChannelStats>;
  turn: ConnectionLogStatsCounters;
  dtls: ConnectionLogDtlsStats;
  packet_loss_simulator: ConnectionLogStatsCounters;
  spotlight: ConnectionLogStatsCounters;
  cc: ConnectionLogStatsCounters;
  ice_connection_state: ConnectionLogIceConnectionStateStats;
  media_publish_worker: ConnectionLogMediaPublishWorkerStats;
};

type ConnectionLogStatsCounters = Record<string, number>;

type ConnectionLogRtpStats = {
  total_received_key_frame: number;
  total_ulpfec_recovered: number;

  total_sent: number;
  total_sent_byte_size: number;

  total_received: number;
  total_received_byte_size: number;

  total_sent_rtp_byte_size: number;
  total_sent_rtp_sfu_delay_us: number;
  total_sent_rtp: number;

  total_received_rtp_byte_size: number;
  total_received_rtp: number;

  total_received_sounding_rtp: number;
  total_received_rtp_payload_invalid: number;

  total_received_rtp_padding: number;
  total_received_rtp_padding_size: number;

  total_received_rtp_rtx: number;

  total_received_rtp_red: number;
  total_received_rtp_red_ulpfec: number;
  total_received_rtp_red_rtx: number;

  total_decrypt_skipped_srtp: number;
  total_decrypt_skipped_video_srtp: number;
  total_decrypt_skipped_audio_srtp: number;

  total_received_srtp_invalid: number;
};

type ConnectionLogSignalingStats = {
  total_received_signaling_pong: number;
  total_sent_signaling_ping: number;
};

type ConnectionLogSimulcastRidStats = {
  rtp: ConnectionLogSimulcastRidRtpStats;
  rtp_hdrext: ConnectionLogStatsCounters;
};

type ConnectionLogSimulcastRidRtpStats = {
  total_decrypt_skipped_srtp: number;
  total_received_rtp: number;
  total_received_rtp_byte_size: number;
  total_received_rtp_padding: number;
  total_received_rtp_padding_size: number;
  total_received_rtp_red: number;
  total_received_rtp_red_rtx: number;
  total_received_rtp_red_ulpfec: number;
  total_received_rtp_rtx: number;
};

type ConnectionLogDtlsStats = {
  total_received_dtls: number;
  total_sent_dtls: number;
};

type ConnectionLogIceConnectionStateStats = {
  total_connected_req: number;
  total_connected_ack: number;
  total_checking_req: number;
  total_checking_ack: number;
  total_disconnected_req: number;
  total_disconnected_ack: number;

  total_connected_to_checking: number;
  total_checking_to_disconnected: number;
  total_disconnected_to_failed: number;
  total_checking_to_connected: number;
  total_disconnected_to_checking: number;

  total_checking_duration_ms: number;
  total_disconnected_duration_ms: number;
};

type ConnectionLogMediaPublishWorkerStats = {
  peak_count: number;
  total_started: number;
  total_stopped: number;
  total_unknown_subscriber: number;
};

type ConnectionLogDataChannelStats = {
  ordered: boolean;
  max_packet_life_time?: number;
  max_retransmits?: number;
  protocol: string;
  direction: string;
  compress: boolean;

  total_data_channel_abandon_message: number;
  total_data_channel_retransmit_message: number;
  total_data_channel_open_message: number;
  total_data_channel_ack_message: number;
  total_received_data_channel_message: number;
  total_received_data_channel_message_byte_size: number;
  total_sent_data_channel_message: number;
  total_sent_data_channel_message_byte_size: number;
};

シグナリングログ

// @ts-check

// シグナリングログ (signaling.jsonl)
type SignalingLog =
  | SignalingLogConnect
  | SignalingLogRedirect
  | SignalingLogOffer
  | SignalingLogAnswer
  | SignalingLogCandidate
  | SignalingLogReOffer
  | SignalingLogReAnswer
  | SignalingLogDisconnect
  | SignalingLogSwitched
  | SignalingLogWebsocketTerminate
  | SignalingLogUnknownType
  | SignalingLogWhipOffer
  | SignalingLogWhipAnswer
  | SignalingLogWhipRedirect
  | SignalingLogWhepOffer
  | SignalingLogWhepAnswer
  | SignalingLogWhepRedirect;

type SignalingLogBase = {
  id: WebhookID;
  channel_id: ChannelId;
  connection_id: ConnectionId;
  timestamp: Timestamp;
  // role が未確定の場合は "-" が出力されます
  role: Role | "-";
};

// type: "connect"
type SignalingLogConnect = SignalingLogBase & {
  type: "connect";
  // type: connect メッセージの内容をそのまま記録します
  json: SignalingConnectMessage;
};

// type: "redirect"
type SignalingLogRedirect = SignalingLogBase & {
  type: "redirect";
  json: SignalingRedirectMessage;
};

// type: "offer"
type SignalingLogOffer = SignalingLogBase & {
  type: "offer";
  sdp: string;
  json: SignalingOfferMessage;
};

// type: "answer"
type SignalingLogAnswer = SignalingLogBase & {
  type: "answer";
  sdp: string;
};

// type: "candidate"
type SignalingLogCandidate = SignalingLogBase & {
  type: "candidate";
  sdp: string;
};

// type: "re-offer"
type SignalingLogReOffer = SignalingLogBase & {
  type: "re-offer";
  sdp: string;
};

// type: "re-answer"
type SignalingLogReAnswer = SignalingLogBase & {
  type: "re-answer";
  sdp: string;
};

// type: "disconnect"
type SignalingLogDisconnect = SignalingLogBase & {
  type: "disconnect";
  type_disconnect_reason?: JSONValue;
};

// type: "switched"
type SignalingLogSwitched = SignalingLogBase & {
  type: "switched";
  ignore_disconnect_websocket: boolean;
};

// type: "websocket-terminate"
type SignalingLogWebsocketTerminate = SignalingLogBase & {
  type: "websocket-terminate";
  reason: SignalingLogWebsocketTerminateReason;
  connected: boolean;
  data_channel_signaling: boolean;
  ignore_disconnect_websocket: boolean;
  // reason が "remote-closed" かつ Cowboy から close コード付きで終了した場合のみ含まれます
  close_code?: number;
  // reason が "other-error" の場合のみ含まれます
  cause?: string;
};

type SignalingLogWebsocketTerminateReason =
  | "remote-closed"
  | "self-closed"
  | "error-closed"
  | "other-error";

// type: "unknown-type"
type SignalingLogUnknownType = SignalingLogBase & {
  type: "unknown-type";
  json: JSONValue;
};

// type: "whip-offer"
type SignalingLogWhipOffer = SignalingLogBase & {
  type: "whip-offer";
  sdp: string;
};

// type: "whip-answer"
type SignalingLogWhipAnswer = SignalingLogBase & {
  type: "whip-answer";
  sdp: string;
};

// type: "whip-redirect"
type SignalingLogWhipRedirect = SignalingLogBase & {
  type: "whip-redirect";
  node: string;
};

// type: "whep-offer"
type SignalingLogWhepOffer = SignalingLogBase & {
  type: "whep-offer";
  sdp: string;
};

// type: "whep-answer"
type SignalingLogWhepAnswer = SignalingLogBase & {
  type: "whep-answer";
  sdp: string;
};

// type: "whep-redirect"
type SignalingLogWhepRedirect = SignalingLogBase & {
  type: "whep-redirect";
  node: string;
};

// シグナリングエラーログ (signaling_error.jsonl)
type SignalingErrorLog = {
  id: WebhookID;
  timestamp: Timestamp;

  version: string;
  label: string;
  node_name: string;

  connection_id: ConnectionId;

  simulcast: boolean;
  spotlight: boolean;

  message: SignalingErrorMessage;

  accepted: boolean;

  sdp_negotiation_state: SignalingErrorLogSdpNegotiationState;

  ipv4_address_list: string[];
  ipv6_address_list: string[];

  data: SignalingErrorLogData;

  // エラー内容に応じて異なる JSON オブジェクトが出力されます
  details: JSONValue;

  config: SignalingErrorLogConfig;
};

type SignalingErrorMessage =
  | "UNAUTHORIZED"
  | "INTERNAL-ERROR"
  | "SERVICE-UNAVAILABLE"
  | "TIMEOUT"
  | "INVALID-MESSAGE";

type SignalingErrorLogSdpNegotiationState =
  | "waiting_connect"
  | "offer_sent"
  | "waiting_answer"
  | "waiting_dtls_ack"
  | "maybe_re_offer"
  | "waiting_re_answer";

type SignalingErrorLogData = {
  role?: Role;
  channel_id?: ChannelId;
  group_id?: SessionId;
  session_id?: SessionId;
  client_id?: ClientId;
  bundle_id?: BundleId;
  audio?: boolean;
  video?: boolean;
  sora_client?: SoraClient;
  // channel_id が不明な場合は含まれません
  stats?: SignalingErrorLogConnectionStats;
};

type SignalingErrorLogConnectionStats = {
  timestamp: Timestamp;
  group_id: SessionId;
  session_id: SessionId;
  channel_id: ChannelId;
  client_id: ClientId;
  bundle_id: BundleId;
  connection_id: ConnectionId;

  signaling: ConnectionLogSignalingStats;
  rtp: ConnectionLogRtpStats;
  rtp_hdrext: ConnectionLogStatsCounters;
  rtcp: ConnectionLogStatsCounters;
  simulcast: Partial<Record<SimulcastRid, ConnectionLogSimulcastRidStats>>;
  sctp: ConnectionLogStatsCounters;
  data_channel: Record<string, ConnectionLogDataChannelStats>;
  turn: ConnectionLogStatsCounters;
  dtls: ConnectionLogDtlsStats;
  packet_loss_simulator: ConnectionLogStatsCounters;
  spotlight: ConnectionLogStatsCounters;
  cc: ConnectionLogStatsCounters;
  simulcast_auto: ConnectionLogStatsCounters;
  ice_connection_state: ConnectionLogIceConnectionStateStats;
  media_publish_worker: ConnectionLogMediaPublishWorkerStats;
};

type SignalingErrorLogConfig = {
  turn: boolean;
  turn_realm: string;
  turn_fqdn: string;

  turn_tcp: boolean;
  turn_tcp_listen_port: number;
  turn_tcp_port: number;

  turn_tls: boolean;
  turn_tls_fqdn: string;
  turn_tls_port: number;

  rtx: boolean;
  ulpfec: boolean;

  connection_created_wait_timeout: number;

  ipv6: boolean;
};

その他のログ

// @ts-check
// JSON Lines 形式ではないログは定義していません。

// RTC 統計ログ
type RtcStatsLog = {
  id: WebhookID;
  timestamp: Timestamp;

  node_name: string;
  label: string;
  version: string;

  type: string;

  role: string;
  channel_id: ChannelId;
  group_id: SessionId;
  session_id: SessionId;
  client_id: ClientId;
  bundle_id: BundleId;
  connection_id: ConnectionId;

  simulcast: boolean;
  spotlight: boolean;

  rtc_timestamp: DOMHighResTimeStamp;
  rtc_type: RTCStatsType;
  rtc_id: string;
  // https://www.w3.org/TR/webrtc/#dom-rtcstatsreport
  rtc_data: Record<string, object>;

  copy_headers?: CopyHeaders;

  log_written: boolean;
};

// API ログの操作種別
type ApiLogOperation =
  | "ChangeMode"
  | "DisconnectChannel"
  | "DisconnectChannelByRole"
  | "DisconnectClient"
  | "DisconnectConnection"
  | "InitCluster"
  | "PurgeClusterNode"
  | "RegisterClusterNode"
  | "StartForwardingRtp"
  | "StartRecording"
  | "StopForwardingRtp"
  | "StopRecording";

// API ログ
type ApiLog = {
  id: WebhookID;
  timestamp: Timestamp;

  // 操作
  operation: ApiLogOperation;

  // JSON データ
  json: string;
};