or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

binary-io.mdbytestring-utilities.mddebug-utilities.mdextension-fields.mdindex.mdmap-operations.mdmessage-operations.md
tile.json

debug-utilities.mddocs/

Debug Utilities

The jspb.debug namespace provides development-time utilities for debugging and inspecting Protocol Buffer messages. These utilities help developers understand message structure and content during development.

Core Debug Functions { .api }

const { debug } = require('google-protobuf');

// Create human-readable representation of a message
const readableObject = debug.dump(message);
console.log(readableObject);

// Example output for a user message:
/*
{
  name: "John Doe",
  age: 25,
  email: "john@example.com",
  addresses: [
    {
      street: "123 Main St",
      city: "Anytown",
      zipCode: "12345"
    },
    {
      street: "456 Oak Ave", 
      city: "Other City",
      zipCode: "67890"
    }
  ],
  preferences: {
    theme: "dark",
    notifications: true,
    language: "en"
  }
}
*/

Type Definitions:

/**
 * @param {!jspb.Message} message Protocol Buffer message instance
 * @return {?Object} Human-readable object representation, or null in production
 */

Important Notes:

  • Only works in unobfuscated/unoptimized code
  • Returns null in production builds or when code is minified
  • Designed for development-time debugging only

Message Introspection { .api }

The debug utilities use reflection to examine message structure:

const { debug } = require('google-protobuf');

// Example message with various field types
class PersonMessage extends Message {
  getName() { return this.name_; }
  setName(value) { this.name_ = value; }
  
  getAge() { return this.age_; }  
  setAge(value) { this.age_ = value; }
  
  getEmailsList() { return this.emails_; }
  setEmailsList(value) { this.emails_ = value; }
  
  getAttributesMap() { return this.attributes_; }
  
  getAddress() { return this.address_; }
  setAddress(value) { this.address_ = value; }
}

// Create and populate message
const person = new PersonMessage();
person.setName("Alice Smith");
person.setAge(30);
person.setEmailsList(["alice@work.com", "alice@personal.com"]);

const address = new AddressMessage();
address.setStreet("789 Pine St");
address.setCity("Springfield");  
person.setAddress(address);

const attrs = person.getAttributesMap();
attrs.set("department", "Engineering");
attrs.set("level", "Senior");

// Debug dump shows structured representation
const dump = debug.dump(person);
/*
{
  name: "Alice Smith",
  age: 30,
  emailsList: ["alice@work.com", "alice@personal.com"],
  address: {
    street: "789 Pine St",
    city: "Springfield"
  },
  attributesMap: {
    department: "Engineering", 
    level: "Senior"
  }
}
*/

Field Name Formatting { .api }

The debug utilities automatically format field names for readability:

const { debug } = require('google-protobuf');

// Generated getters typically use TitleCase
class ExampleMessage extends Message {
  getUserName() { return this.user_name_; }
  setUserName(value) { this.user_name_ = value; }
  
  getAccountID() { return this.account_id_; }
  setAccountID(value) { this.account_id_ = value; }
  
  getIsActiveUser() { return this.is_active_user_; }
  setIsActiveUser(value) { this.is_active_user_ = value; }
}

const message = new ExampleMessage();
message.setUserName("john_doe");
message.setAccountID("12345");
message.setIsActiveUser(true);

const dump = debug.dump(message);
/*
Field names are converted from TitleCase getters to camelCase:
{
  userName: "john_doe",      // getUserName -> userName
  accountID: "12345",        // getAccountID -> accountID  
  isActiveUser: true         // getIsActiveUser -> isActiveUser
}
*/

Nested Message Debugging { .api }

Debug utilities recursively process nested messages and complex structures:

const { debug } = require('google-protobuf');

// Complex nested message structure
class CompanyMessage extends Message {
  getName() { return this.name_; }
  
  getDepartmentsList() { return this.departments_; }
  
  getOfficesMap() { return this.offices_; }
}

class DepartmentMessage extends Message {
  getName() { return this.name_; }
  
  getEmployeesList() { return this.employees_; }
  
  getBudget() { return this.budget_; }
}

class EmployeeMessage extends Message {
  getName() { return this.name_; }
  
  getPosition() { return this.position_; }
  
  getSalary() { return this.salary_; }
}

// Build complex message hierarchy
const company = new CompanyMessage();
company.setName("TechCorp");

const engineering = new DepartmentMessage();
engineering.setName("Engineering");
engineering.setBudget(1000000);

const alice = new EmployeeMessage();
alice.setName("Alice Johnson");
alice.setPosition("Senior Developer");
alice.setSalary(85000);

const bob = new EmployeeMessage();
bob.setName("Bob Smith");
bob.setPosition("Tech Lead");
bob.setSalary(95000);

engineering.setEmployeesList([alice, bob]);
company.setDepartmentsList([engineering]);

// Debug dump shows full hierarchy
const dump = debug.dump(company);
/*
{
  name: "TechCorp",
  departmentsList: [
    {
      name: "Engineering",
      budget: 1000000,
      employeesList: [
        {
          name: "Alice Johnson",
          position: "Senior Developer", 
          salary: 85000
        },
        {
          name: "Bob Smith",
          position: "Tech Lead",
          salary: 95000
        }
      ]
    }
  ]
}
*/

Map Field Debugging { .api }

Maps are converted to plain JavaScript objects for easier inspection:

const { debug, Map } = require('google-protobuf');

class ConfigMessage extends Message {
  getSettingsMap() { return this.settings_; }
  
  getServersMap() { return this.servers_; }
}

class ServerMessage extends Message {
  getHost() { return this.host_; }
  getPort() { return this.port_; }
  getStatus() { return this.status_; }
}

// Create message with map fields
const config = new ConfigMessage();

// String-to-string map
const settings = config.getSettingsMap();
settings.set("debug_mode", "true");
settings.set("log_level", "info");
settings.set("timeout", "30");

// String-to-message map
const servers = config.getServersMap();

const webServer = new ServerMessage();
webServer.setHost("web.example.com");
webServer.setPort(80);
webServer.setStatus("active");

const apiServer = new ServerMessage();
apiServer.setHost("api.example.com");
apiServer.setPort(443);
apiServer.setStatus("maintenance");

servers.set("web", webServer);
servers.set("api", apiServer);

// Debug dump converts maps to objects
const dump = debug.dump(config);
/*
{
  settingsMap: {
    debug_mode: "true",
    log_level: "info", 
    timeout: "30"
  },
  serversMap: {
    web: {
      host: "web.example.com",
      port: 80,
      status: "active"
    },
    api: {
      host: "api.example.com", 
      port: 443,
      status: "maintenance"
    }
  }
}
*/

Extension Field Debugging { .api }

Extension fields are included in debug output when available:

const { debug, ExtensionFieldInfo } = require('google-protobuf');

// Define extension field
const customExtension = new ExtensionFieldInfo(
  1001,                    // Field number
  {custom_data: 0},        // Field name object
  null,                    // No constructor (primitive)
  null,                    // No toObject function
  0                        // Not repeated
);

const userExtension = new ExtensionFieldInfo(
  1002,
  {user_metadata: 0},
  UserMetadataMessage,     // Message constructor
  null,                    // Use default toObject
  0
);

// Use extensions
const message = new BaseMessage();
message.setExtension(customExtension, "custom value");

const metadata = new UserMetadataMessage();
metadata.setCreatedBy("admin");
metadata.setCreatedAt("2023-01-01");
message.setExtension(userExtension, metadata);

// Debug dump includes extensions
const dump = debug.dump(message);
/*
{
  // Regular fields...
  name: "Base Message",
  
  // Extensions appear in the dump
  customData: "custom value",
  userMetadata: {
    createdBy: "admin",
    createdAt: "2023-01-01"
  }
}
*/

Development Workflow Integration { .api }

Console Debugging

const { debug } = require('google-protobuf');

// Quick message inspection during development
function debugMessage(message, label = "Message") {
  const dump = debug.dump(message);
  if (dump) {
    console.group(label);
    console.log(JSON.stringify(dump, null, 2));
    console.groupEnd();
  } else {
    console.log(`${label}: [Debug info not available in production]`);
  }
}

// Usage in development
const user = new UserMessage();
user.setName("Test User");
user.setEmail("test@example.com");

debugMessage(user, "Created User");

// Modify and debug again
user.setAge(25);
debugMessage(user, "Updated User");

Testing Integration

const { debug } = require('google-protobuf');

// Test helper for message comparison
function assertMessagesEqual(expected, actual, testName) {
  const expectedDump = debug.dump(expected);
  const actualDump = debug.dump(actual);
  
  if (expectedDump && actualDump) {
    const expectedJson = JSON.stringify(expectedDump, null, 2);
    const actualJson = JSON.stringify(actualDump, null, 2);
    
    if (expectedJson !== actualJson) {
      console.error(`Test failed: ${testName}`);
      console.error("Expected:", expectedJson);
      console.error("Actual:", actualJson);
      throw new Error(`Message mismatch in ${testName}`);
    }
  } else {
    // Fallback to binary comparison in production
    const expectedBytes = expected.serializeBinary();
    const actualBytes = actual.serializeBinary();
    
    if (expectedBytes.length !== actualBytes.length ||
        !expectedBytes.every((b, i) => b === actualBytes[i])) {
      throw new Error(`Message mismatch in ${testName}`);
    }
  }
}

// Usage in tests
const originalMessage = createTestMessage();
const serialized = originalMessage.serializeBinary();
const deserialized = TestMessage.deserializeBinary(serialized);

assertMessagesEqual(originalMessage, deserialized, "Serialization Round-trip");

Error Debugging

const { debug } = require('google-protobuf');

// Enhanced error reporting with message state
function processMessage(message) {
  try {
    // Process message logic
    return performComplexOperation(message);
  } catch (error) {
    // Include message state in error reporting
    const messageState = debug.dump(message);
    
    console.error("Processing failed for message:", messageState);
    console.error("Error:", error.message);
    console.error("Stack:", error.stack);
    
    throw new Error(`Message processing failed: ${error.message}`);
  }
}

Production Considerations { .api }

Build Configuration

// The debug utilities check for code optimization
const { debug } = require('google-protobuf');

function safeDebugDump(message) {
  const dump = debug.dump(message);
  
  if (dump === null) {
    // Production build - debug info not available
    return {
      message: "[Debug information not available in optimized build]",
      type: message.constructor.name || "Unknown",
      serialized: message.serializeBinary().length + " bytes"
    };
  }
  
  return dump;
}

// Always safe to call
console.log("Message state:", safeDebugDump(message));

Conditional Debugging

const { debug } = require('google-protobuf');

// Only use debug utilities in development
const isDevelopment = process.env.NODE_ENV === 'development';

function conditionalDebug(message, label) {
  if (!isDevelopment) {
    return; // Skip debug output in production
  }
  
  const dump = debug.dump(message);
  if (dump) {
    console.log(`[DEBUG] ${label}:`, dump);
  }
}

// Safe for production deployment
conditionalDebug(userMessage, "User Registration");

Performance Impact

Development vs Production

// Debug operations have minimal overhead when optimized away
const { debug } = require('google-protobuf');

function benchmarkMessage(message, iterations = 1000) {
  const start = performance.now();
  
  for (let i = 0; i < iterations; i++) {
    const dump = debug.dump(message);  // No-op in production
    
    // Prevent optimization
    if (dump) {
      doSomethingWithDump(dump);
    }
  }
  
  const end = performance.now();
  console.log(`Debug operations: ${end - start}ms for ${iterations} calls`);
}

// Minimal overhead in production builds
benchmarkMessage(testMessage);

Memory Considerations

// Debug dumps create temporary objects
const { debug } = require('google-protobuf');

function processLargeMessageList(messages) {
  return messages.map(message => {
    // Process message
    const result = processMessage(message);
    
    // Debug in development only - avoid memory overhead in production
    if (process.env.NODE_ENV === 'development') {
      const dump = debug.dump(result);
      if (dump && Object.keys(dump).length > 10) {
        console.warn("Large message detected:", Object.keys(dump).length, "fields");
      }
    }
    
    return result;
  });
}