Actors provide a built-in way to log complex data to the console.

When dealing with lots of data, console.log often doesn’t cut it. Using the context’s log object (c.log) allows you to log complex data using structured logging.

Using the actor logging API is completely optional.

Log levels

There are 5 log levels:

LevelCallDescription
Criticalc.log.critical(message, ...args);Severe errors that prevent core functionality
Errorc.log.error(message, ...args);Errors that affect functionality but allow continued operation
Warningc.log.warn(message, ...args);Potentially harmful situations that should be addressed
Infoc.log.info(message, ...args);General information about significant events & state changes
Debugc.log.debug(message, ...args);Detailed debugging information, usually used only in development

Structured logging

The built-in logging API (using c.log) provides structured logging to let you log key-value pairs instead of raw strings. Structures logs are readable by both machines & humans to make them easier to parse & search.

Passing an object to a log will print as structured data. For example:

c.log.info('increment', { connection: c.conn.id, count });
// Prints: level=INFO msg=increment connection=123 count=456

The first parameter in each log method is the message. The rest of the arguments are used for structured logging.

c.log vs console.log logging

c.log makes it easier to manage complex logs, while console.log can become unmaintainable at scale.

Consider this example:

import { actor } from "actor-core";

const counter = actor({
  state: { count: 0 },
  
  actions: {
    increment: (c, count) => {
      // Prints: level=INFO msg=increment connection=123 count=456
      c.log.info('increment', { connection: c.conn.id, count });

      c.state.count += count;
      return c.state.count;
    }
  }
});

If you need to search through a lot of logs, it’s easier to read the structured logs. To find increments for a single connection, you can search connection=123.

Additionally, structured logs can be parsed and queried at scale using tools like Elasticsearch, Loki, or Datadog. For example, you can parse the log level=INFO msg=increment connection=123 count=456 in to the JSON object {"level":"INFO","msg":"increment","connection":123,"count":456} and then query it as you would any other structured data.

Usage in lifecycle hooks

The logger is available in all lifecycle hooks:

import { actor } from "actor-core";

const loggingExample = actor({
  state: { events: [] },
  
  onStart: (c) => {
    c.log.info('actor_started', { timestamp: Date.now() });
  },
  
  onBeforeConnect: (c, { params }) => {
    c.log.debug('connection_attempt', { 
      ip: params.ip,
      timestamp: Date.now() 
    });
    
    return { authorized: true };
  },
  
  onConnect: (c) => {
    c.log.info('connection_established', { 
      connectionId: c.conn.id,
      timestamp: Date.now() 
    });
    
    c.state.events.push({
      type: 'connect',
      connectionId: c.conn.id,
      timestamp: Date.now()
    });
  },
  
  onDisconnect: (c) => {
    c.log.info('connection_closed', { 
      connectionId: c.conn.id,
      timestamp: Date.now() 
    });
    
    c.state.events.push({
      type: 'disconnect',
      connectionId: c.conn.id,
      timestamp: Date.now()
    });
  },
  
  actions: {
    // Actor actions...
  }
});