petitviolet blog

    Well-formatted logs for Google Cloud Logging

    2025-03-20

    Node.jsGCP

    In writing logs in applications that run on Google Cloud Platform, it requires some configurations to be compiled to trace logs in Cloud Logging. This post describes how to implement it, and also some tips for making logs more useful and informative.

    Tracked as trace logs in Cloud Logging

    Node.js applications can out logs with just using console.log or console.error, and they will be collected and can be seen in Cloud Logging. However, they are not compiled to trace logs. It means that it's hard to know which request the log belongs to.

    The following code is an example of writing logs to get compiled to trace logs.

    const writeLog = (req: Request, message: string): void => {
      const project = process.env.GOOGLE_CLOUD_PROJECT
      const traceHeader = req.headers.get('X-Cloud-Trace-Context')
      const [trace] = traceHeader.split('/')
      const base = {
        url: req.url,
        'logging.googleapis.com/trace': `projects/${project}/traces/${trace}`,
      }
      console.log({
        message,
        ...base,
        severity: 'INFO',
      })
    }
    

    Google Cloud tweaks requests to inject X-Cloud-Trace-Context header to requests for tracing. Inserting the X-Cloud-Trace-Context header value in logging.googleapis.com/trace field makes the log be compiled to trace logs.

    See Logging agent: special JSON fields for more details.

    Including Stack Trace

    In error logs, it's very useful to include stack trace for diagnosing. From here, I'll show how to implement it using winston. winston provides errors({ stack: true }) to include stack trace as the document shows, but it only works when given object is an instance of Error.

    const error = new Error('test')
    logger.error(error) // includes stack trace, but no additional message
    logger.error({ message: 'failed', error }) // has additional message, but no stack trace
    

    To solve this, it needs to implement a custom format like the following.

    import winston from 'winston'
    
    const includeStackTrace = winston.format(
      (log): winston.Logform.TransformableInfo => {
        const severity = log.level.toUpperCase()
        const error = (() => {
          if ('error' in log) {
            if ((log as any).error instanceof Error) {
              return log.error.toString()
            }
          } else if (typeof log.message === 'object' && 'error' in log.message) {
            if ((log.message as any).error instanceof Error) {
              return (log.message as any).error.toString()
            }
          }
          return log.error
        })()
        const stack = (() => {
          if (
            'error' in log &&
            (log as any).error instanceof Error &&
            (log as any).error.stack &&
            !('stack' in log)
          ) {
            return (log as any).error.stack
          } else if (
            typeof log.message === 'object' &&
            'error' in log.message &&
            'stack' in (log.message as any).error &&
            !('stack' in log.message)
          ) {
            return (log.message as any).error.stack
          }
        })()
        const result = Object.assign({}, log, {
          severity,
          stack,
          error,
        })
        return result
      },
    )
    

    This allows logs to include the stack trace of an Error object that is passed in the error field of the given object. You can use this format as below:

    const {
      combine,
      json,
      timestamp,
      errors,
      uncolorize,
      prettyPrint,
    } = winston.format
    
    const format = combine(
      errors({ stack: true }),
      timestamp({ format: 'YYYY-MM-DD_HH:mm:ss' }),
      includeStackTrace(),
      prettyPrint(),
      json(),
      uncolorize(),
    )
    

    Create Logger

    Creating a logger looks like the following code snippet:

    import winston from 'winston'
    
    const logger = winston.createLogger({
      level: 'info',
      format,
      transports: [new winston.transports.Console()],
    })
    

    What logs are written looks like is:

    cloud logging trace

    References