mirror of https://github.com/mastodon/mastodon
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			120 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			120 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			JavaScript
		
	
const { pino } = require('pino');
 | 
						|
const { pinoHttp, stdSerializers: pinoHttpSerializers } = require('pino-http');
 | 
						|
const uuid = require('uuid');
 | 
						|
 | 
						|
/**
 | 
						|
 * Generates the Request ID for logging and setting on responses
 | 
						|
 * @param {http.IncomingMessage} req
 | 
						|
 * @param {http.ServerResponse} [res]
 | 
						|
 * @returns {import("pino-http").ReqId}
 | 
						|
 */
 | 
						|
function generateRequestId(req, res) {
 | 
						|
  if (req.id) {
 | 
						|
    return req.id;
 | 
						|
  }
 | 
						|
 | 
						|
  req.id = uuid.v4();
 | 
						|
 | 
						|
  // Allow for usage with WebSockets:
 | 
						|
  if (res) {
 | 
						|
    res.setHeader('X-Request-Id', req.id);
 | 
						|
  }
 | 
						|
 | 
						|
  return req.id;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Request log sanitizer to prevent logging access tokens in URLs
 | 
						|
 * @param {http.IncomingMessage} req
 | 
						|
 */
 | 
						|
function sanitizeRequestLog(req) {
 | 
						|
  const log = pinoHttpSerializers.req(req);
 | 
						|
  if (typeof log.url === 'string' && log.url.includes('access_token')) {
 | 
						|
    // Doorkeeper uses SecureRandom.urlsafe_base64 per RFC 6749 / RFC 6750
 | 
						|
    log.url = log.url.replace(/(access_token)=([a-zA-Z0-9\-_]+)/gi, '$1=[Redacted]');
 | 
						|
  }
 | 
						|
  return log;
 | 
						|
}
 | 
						|
 | 
						|
const logger = pino({
 | 
						|
  name: "streaming",
 | 
						|
  // Reformat the log level to a string:
 | 
						|
  formatters: {
 | 
						|
    level: (label) => {
 | 
						|
      return {
 | 
						|
        level: label
 | 
						|
      };
 | 
						|
    },
 | 
						|
  },
 | 
						|
  redact: {
 | 
						|
    paths: [
 | 
						|
      'req.headers["sec-websocket-key"]',
 | 
						|
      // Note: we currently pass the AccessToken via the websocket subprotocol
 | 
						|
      // field, an anti-pattern, but this ensures it doesn't end up in logs.
 | 
						|
      'req.headers["sec-websocket-protocol"]',
 | 
						|
      'req.headers.authorization',
 | 
						|
      'req.headers.cookie',
 | 
						|
      'req.query.access_token'
 | 
						|
    ]
 | 
						|
  }
 | 
						|
});
 | 
						|
 | 
						|
const httpLogger = pinoHttp({
 | 
						|
  logger,
 | 
						|
  genReqId: generateRequestId,
 | 
						|
  serializers: {
 | 
						|
    req: sanitizeRequestLog
 | 
						|
  }
 | 
						|
});
 | 
						|
 | 
						|
/**
 | 
						|
 * Attaches a logger to the request object received by http upgrade handlers
 | 
						|
 * @param {http.IncomingMessage} request
 | 
						|
 */
 | 
						|
function attachWebsocketHttpLogger(request) {
 | 
						|
  generateRequestId(request);
 | 
						|
 | 
						|
  request.log = logger.child({
 | 
						|
    req: sanitizeRequestLog(request),
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates a logger instance for the Websocket connection to use.
 | 
						|
 * @param {http.IncomingMessage} request
 | 
						|
 * @param {import('./index.js').ResolvedAccount} resolvedAccount
 | 
						|
 */
 | 
						|
function createWebsocketLogger(request, resolvedAccount) {
 | 
						|
  // ensure the request.id is always present.
 | 
						|
  generateRequestId(request);
 | 
						|
 | 
						|
  return logger.child({
 | 
						|
    req: {
 | 
						|
      id: request.id
 | 
						|
    },
 | 
						|
    account: {
 | 
						|
      id: resolvedAccount.accountId ?? null
 | 
						|
    }
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
exports.logger = logger;
 | 
						|
exports.httpLogger = httpLogger;
 | 
						|
exports.attachWebsocketHttpLogger = attachWebsocketHttpLogger;
 | 
						|
exports.createWebsocketLogger = createWebsocketLogger;
 | 
						|
 | 
						|
/**
 | 
						|
 * Initializes the log level based on the environment
 | 
						|
 * @param {Object<string, any>} env
 | 
						|
 * @param {string} environment
 | 
						|
 */
 | 
						|
exports.initializeLogLevel = function initializeLogLevel(env, environment) {
 | 
						|
  if (env.LOG_LEVEL && Object.keys(logger.levels.values).includes(env.LOG_LEVEL)) {
 | 
						|
    logger.level = env.LOG_LEVEL;
 | 
						|
  } else if (environment === 'development') {
 | 
						|
    logger.level = 'debug';
 | 
						|
  } else {
 | 
						|
    logger.level = 'info';
 | 
						|
  }
 | 
						|
};
 |