lazywithclass/winston-cloudwatch

Enhanced logs to be sent to cloudwatch when using `logform.format`

Opened this issue · 0 comments

Problem

log

{
	id: 'database-connection',
	duration: 10,
	timestamp: '2024-02-24-33'
	body: {
		host: 'aws-mysql-some-where',
		port: 25511,
	}
}

Description

The winston package supports a flexible logging format, so you can write logs in the format shown above. However, if you write logs like this, winston-cloudwatch will not send them. In order for winston-cloudwatch to send logs, you need to change them to the following form.

{
	message: {
		id: 'database-connection',
		duration: 10,
		timestamp: '2024-02-24-33'
		body: {
			host: 'aws-mysql-some-where',
			port: 25511,
		}
	}
}

winston-cloudwatch inspects the message field when sending logs and will not send logs to cloudwatch if the message field is empty. In order to send logs to cloudwatch, the message field must be filled with data. Using the winston formatter to solve this problem does not work. The latest version of winston-transport runs the format.transform function before the log function is executed and passes the result as input to the log function. It does not change the info object, but instead appends the transformed message to the value of the Symbol(message) key, so the message field remains empty. The messageFormatter option doesn't help to fill the message field because it works just before sending the log.

{
	id: 'database-connection',
	duration: 10,
	timestamp: '2024-02-24-33'
	body: {
		host: 'aws-mysql-some-where',
		port: 25511,
	}
  [Symbol(message)]: '{"id":"database-connection","duration":10,"timestamp":"2024-02-24-33","body":{"host":"aws-mysql-some-where","port":25511}}'
}

Because winston-cloudwatch only inspects the message field and not the Symbol(message) field, logs cannot be sent normally in this case.

Environments

  • node 18.18.2
  • winston 3.11.0
  • winston-cloudwatch 6.2.0
  • @aws-sdk/client-cloudwatch-logs 3.515.0

Expects

winston-cloudwatch should be able to send messages even when Symbol(message) is filled with log data using the format function provided by winston-transport. There are two approaches to solve this.

transform logging object

This is a way to transform the info object passed to the log function. For example like that,

WinstonCloudWatch.prototype.message = function (info) {
  try {
    if (info == null) {
      return info;
    }

    if (!isEmpty(log.message) || isError(log.message)) {
      return info;
    }

    if (info[Symbol.for('message')] !== null) {
      return { message: info[Symbol.for('message')] };
    }
  } catch {
    return info;
  }
}

If the message field in the info object is empty, retrieve the value of Symbol(message) and if it is not empty, change the info object form. The changed info object has a message field, so it works well with log functions, add functions, etc.

change isEmpty

Change the logic to inspect if info object is an empty object.

WinstonCloudWatch.prototype.isEmpty = function (info) {
  try {
    if (!isEmpty(info.message) || isError(info.message)) { 
      return false;
    }

    if (isEmpty(info[Symbol.for('message')]) || isError(info[Symbol.for('message')])) {
      return false;
    }

    return true;
  } catch {
    return true;
  }
}

However, this approach requires modifying the add function. The formatMessage function will expect to receive either info.message or info[Symbol.for('message') as input, so we need to change the logic around that.

  if (!self.isEmpty(log)) {
    self.logEvents.push({
      message: self.formatMessage(log), // need to change the logic around that,
      timestamp: new Date().getTime()
    });
  }

conclusions

I've modified it with the transform info object method to verify normal behavior, and hope to issue a PR if that's acceptable.