Log level for verbosity (error, warn, info, debug, trace)

When working with the Zoom Realtime Media Streams (RTMS) SDK, understanding how to control logging is essential for debugging, monitoring, and integrating with your existing logging infrastructure. This guide covers everything you need to know about RTMS SDK logging.

Quick start with environment variables

The RTMS Quickstart app provides a .env.example file that demonstrates the three key logging parameters:

# Log level for verbosity (error, warn, info, debug, trace)
ZM_RTMS_LOG_LEVEL=info
# Log format (progressive for human-readable, json for machine-readable)
ZM_RTMS_LOG_FORMAT=progressive
# Enable or disable logging
ZM_RTMS_LOG_ENABLED=true

Simply copy .env.example to .env and configure these values before running your application.

Understanding log levels

The RTMS SDK supports five log levels, from least to most verbose:

LevelValueUse Case
error0Production - critical errors only
warn1Production - errors and warnings
info2Development - general information (default)
debug3Debugging - detailed diagnostic info
trace4Deep debugging - includes all SDK operations

Log format options

Progressive format (human-readable)

Best for development and console output:

ZM_RTMS_LOG_FORMAT=progressive

Output example:

rtms     | 2025-10-03T10:15:30.123Z | INFO  | Connecting to meeting
rtms     | 2025-10-03T10:15:31.456Z | DEBUG | WebSocket connection established

JSON format (machine-readable)

Ideal for log aggregation systems like Elasticsearch, Splunk, or CloudWatch:

ZM_RTMS_LOG_FORMAT=json

Output example:

{"timestamp":"2025-10-03T10:15:30.123Z","level":"info","component":"rtms","message":"Connecting to meeting"}
{"timestamp":"2025-10-03T10:15:31.456Z","level":"debug","component":"rtms","message":"WebSocket connection established"}

Redirecting SDK logs

The RTMS SDK writes to stdout. Use standard shell redirection to send logs to files or processing pipelines:

Redirect to file

# All output to file
npm start > rtms.log 2>&1
# Only stdout (SDK logs) to file
npm start > rtms.log
# Separate stdout and stderr
npm start > rtms.log 2> errors.log

Pipe to log processor

# Send to journald
npm start | systemd-cat -t rtms-app
# Process with jq (for JSON format)
npm start | jq -r '.message'
# Stream to remote syslog
npm start | nc syslog-server 514

Dockerized applications

In your Dockerfile or docker-compose.yml:

services:
    rtms-app:
        build: .
        environment:
            - ZM_RTMS_LOG_LEVEL=info
            - ZM_RTMS_LOG_FORMAT=json
        logging:
            driver: "json-file"
            options:
                max-size: "10m"
                max-file: "3"

Programmatic configuration

Override environment variables at runtime using the configureLogger() function:

import rtms from "@zoom/rtms";
// Configure logging programmatically
rtms.configureLogger({
    level: rtms.LogLevel.DEBUG,
    format: rtms.LogFormat.JSON,
    enabled: true,
});

This is useful for:

  • Dynamic log level adjustment based on runtime conditions
  • Different logging configurations per environment
  • Testing scenarios requiring specific log outputs

Disabling SDK logging for custom solutions

If you prefer to use your own logging framework (Winston, Pino, Bunyan, etc.), disable the SDK's internal logging and implement logging in the callbacks:

Step 1: Disable SDK logging

# In .env
ZM_RTMS_LOG_ENABLED=false

Or programmatically:

rtms.configureLogger({ enabled: false });

Step 2: Implement custom logging in callbacks

Here's a complete example using a custom logger:

import rtms from "@zoom/rtms";
import winston from "winston";
// Set up your custom logger
const logger = winston.createLogger({
    level: "info",
    format: winston.format.json(),
    transports: [
        new winston.transports.File({
            filename: "rtms-error.log",
            level: "error",
        }),
        new winston.transports.File({ filename: "rtms-combined.log" }),
    ],
});
// Disable SDK logging
rtms.configureLogger({ enabled: false });
const client = new rtms.Client();
// Log join confirmation
client.onJoinConfirm((reason) => {
    logger.info("Meeting joined", {
        reason,
        timestamp: Date.now(),
    });
});
// Log session updates
client.onSessionUpdate((op, sessionInfo) => {
    logger.info("Session update", {
        operation: op,
        sessionId: sessionInfo.sessionId,
        status: sessionInfo.status,
    });
});
// Log participant changes
client.onUserUpdate((op, participantInfo) => {
    logger.info("User update", {
        operation: op,
        userId: participantInfo.userId,
        userName: participantInfo.userName,
        eventType: op === rtms.USER_EVENT_JOIN ? "join" : "leave",
    });
});
// Log media data with metrics
client.onAudioData((buffer, size, timestamp, metadata) => {
    logger.debug("Audio data received", {
        userName: metadata.userName,
        userId: metadata.userId,
        size,
        timestamp,
    });
});
client.onVideoData((buffer, size, timestamp, trackId, metadata) => {
    logger.debug("Video data received", {
        userName: metadata.userName,
        trackId,
        size,
        timestamp,
    });
});
client.onTranscriptData((buffer, size, timestamp, metadata) => {
    const text = buffer.toString("utf8");
    logger.info("Transcript received", {
        userName: metadata.userName,
        text,
        timestamp,
    });
});
// Log disconnection
client.onLeave((reason) => {
    logger.warn("Meeting ended", {
        reason,
        timestamp: Date.now(),
    });
});

Production best practices

1. Use JSON format for production

ZM_RTMS_LOG_FORMAT=json
ZM_RTMS_LOG_LEVEL=warn

JSON logs integrate seamlessly with log aggregation platforms and make querying much easier.

2. Implement structured logging

Add context to your callback logs:

client.onAudioData((buffer, size, timestamp, metadata) => {
    logger.info({
        event: "audio_received",
        meeting_id: client.uuid(),
        stream_id: client.streamId(),
        participant: {
            user_id: metadata.userId,
            user_name: metadata.userName,
        },
        metrics: {
            size_bytes: size,
            timestamp: timestamp,
        },
    });
});

3. Monitor key metrics

Track important events for monitoring:

const metrics = {
    audioFrames: 0,
    videoFrames: 0,
    participants: new Set(),
};
client.onAudioData((buffer, size, timestamp, metadata) => {
    metrics.audioFrames++;
    metrics.participants.add(metadata.userId);
});
// Log metrics periodically
setInterval(() => {
    logger.info("RTMS metrics", metrics);
    metrics.audioFrames = 0;
    metrics.videoFrames = 0;
}, 60000); // Every minute

4. Handle errors gracefully

Always log error scenarios:

client.onLeave((reason) => {
    if (reason !== 0) {
        logger.error("Abnormal disconnect", {
            reason_code: reason,
            meeting_id: client.uuid(),
        });
        // Implement reconnection logic here
    }
});

Debugging tips

Enable trace logging temporarily

// Enable detailed logging for debugging
if (process.env.NODE_ENV !== "production") {
    rtms.configureLogger({
        level: rtms.LogLevel.TRACE,
        format: rtms.LogFormat.PROGRESSIVE,
    });
}

Filter logs by component

When using JSON format, filter by component:

npm start | jq 'select(.component == "rtms")'

Correlate logs with meeting IDs

Always include meeting context:

const meetingContext = {
    uuid: client.uuid(),
    streamId: client.streamId(),
};
logger.info("Processing started", meetingContext);

Resources

Summary

The RTMS SDK provides flexible logging options through environment variables or programmatic configuration. For production use, disable SDK logging and implement custom logging within the callback functions to maintain full control over your logging infrastructure. This approach allows you to:

  • Integrate with existing logging frameworks
  • Add business-specific context to logs
  • Control log retention and rotation policies
  • Implement custom alerting and monitoring

Need help? Join the conversation on the Zoom Developer Forum!