gcp-structured-logger
Advanced tools
Comparing version
/// <reference types="express-serve-static-core" /> | ||
/// <reference types="next" /> | ||
import { Request, RequestHandler, ErrorRequestHandler } from 'express-serve-static-core'; | ||
@@ -6,2 +7,3 @@ import { LogSeverity } from "./src/severity"; | ||
import { requestToHttpRequest } from "./src/request-transformers"; | ||
import { NextRequest as _NextRequest } from 'next/server' | ||
@@ -47,3 +49,3 @@ /** @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#httprequest */ | ||
} | ||
export type ExtractUser = (req: Request) => string | null | void; | ||
export type ExtractUser = (req: Request | _NextRequest) => string | null | void; | ||
export type Transport = (entry: TransportLogEntry, data: string | { message?: string, [k: string]: any }) => void; | ||
@@ -76,2 +78,3 @@ export interface LoggingConfig { | ||
makeErrorMiddleware(): ErrorRequestHandler; | ||
nextJSMiddleware(req: _NextRequest): void; | ||
/** @returns A function to call to detach from the process. */ | ||
@@ -88,4 +91,8 @@ attachToProcess(loggingTo: StructuredLogger): () => void; | ||
} | ||
declare interface NextRequest extends _NextRequest { | ||
readonly log: StructuredRequestLogger; | ||
} | ||
} | ||
export { requestToHttpRequest, LogSeverity }; |
@@ -21,3 +21,3 @@ const { finished } = require('stream') | ||
* @private | ||
* @param {import('express-serve-static-core').Request} req | ||
* @param {import('./src/StructuredLogger').Request} req | ||
*/ | ||
@@ -57,2 +57,9 @@ _makeRequestLog(req) { | ||
/** | ||
* @param {import('next/server').NextRequest} req | ||
*/ | ||
nextJSMiddleware(req) { | ||
Object.defineProperty(req, 'log', { value: this._makeRequestLog(req), enumerable: true, configurable: false }) | ||
} | ||
/** | ||
* @param {StructuredLogger} loggingTo | ||
@@ -59,0 +66,0 @@ * @returns A function to call to detach from the process |
{ | ||
"name": "gcp-structured-logger", | ||
"version": "1.4.4", | ||
"version": "1.4.5", | ||
"description": "Structured logger for GCP logging", | ||
@@ -43,4 +43,5 @@ "main": "index.js", | ||
"mocha": "^10.2.0", | ||
"next": "^12.3.4", | ||
"sinon": "^15.0.3" | ||
} | ||
} |
# GCP Structured Logger | ||
[](https://github.com/bookcreator/gcp-structured-logger/actions?query=workflow%3A%22Node.js+CI%22) | ||
[](https://www.npmjs.org/package/gcp-structured-logger) | ||
@@ -68,3 +69,3 @@ Outputs [structured logs](https://cloud.google.com/run/docs/logging#writing_structured_logs) that are formatted in GCP logging. | ||
```js | ||
const express = require('express') express() | ||
const express = require('express') | ||
@@ -91,2 +92,20 @@ const app = express() | ||
## With NextJS | ||
Can be use Next.js in the [middleware file](https://nextjs.org/docs/app/building-your-application/routing/middleware#convention), it should be added as the first middleware (to allow you to use `req.log` in future middlewares). | ||
This then adds the `.log` property onto all requests. | ||
```ts | ||
import { NextResponse } from 'next/server' | ||
import type { NextRequest } from 'next/server' | ||
export function middleware(request: NextRequest) { | ||
logger.nextJSMiddleware(request); | ||
// Continue or do usual middleware handling | ||
return NextResponse.next(); | ||
} | ||
``` | ||
You can also pass in a `requestUserExtractor` function when creating a `Logging` instance for setting the [user](https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorContext#FIELDS.user) of the error. | ||
@@ -93,0 +112,0 @@ |
@@ -0,5 +1,9 @@ | ||
const { getUrl, getHeader, getProtocol, getRemoteIp, getResponse } = require('./request-properties') | ||
/** @typedef {import('./StructuredLogger').Request} Request */ | ||
/** | ||
* Converts a request (and its attached response) to a `HttpRequest` for Stackdriver `LogEntry`. | ||
* | ||
* @param {import('express-serve-static-core').Request} req | ||
* @param {Request} req | ||
* @returns {import('../').LoggingHttpRequest} | ||
@@ -9,3 +13,3 @@ */ | ||
// Copy from reporting | ||
const { url, method, ...reportingReq } = requestToErrorReportingHttpRequest(req) | ||
const { url, method, responseStatusCode, referrer, ...reportingReq } = requestToErrorReportingHttpRequest(req) | ||
/** @type {import('../').LoggingHttpRequest} */ | ||
@@ -15,16 +19,19 @@ const httpReq = { | ||
requestMethod: method, | ||
...reportingReq, | ||
} | ||
if ('remoteIp' in reportingReq) httpReq.remoteIp = reportingReq.remoteIp | ||
if ('referrer' in reportingReq) httpReq.referer = reportingReq.referrer | ||
if ('userAgent' in reportingReq) httpReq.userAgent = reportingReq.userAgent | ||
if ('responseStatusCode' in reportingReq) httpReq.status = reportingReq.responseStatusCode | ||
if (referrer !== undefined) httpReq.referer = referrer | ||
if (responseStatusCode !== undefined) httpReq.status = responseStatusCode | ||
// Add in extra request info | ||
const requestSize = req.get('content-length') | ||
const protocol = getProtocol(req) | ||
if (protocol !== undefined) httpReq.protocol = protocol | ||
const requestSize = getHeader(req, 'content-length') | ||
if (requestSize !== undefined) httpReq.requestSize = parseInt(requestSize) | ||
if (req.protocol) httpReq.protocol = req.protocol + '/' + req.httpVersion | ||
if (req.res) { | ||
const res = getResponse(req) | ||
if (res) { | ||
// Response info | ||
const responseSize = req.res.get('content-length') | ||
const responseSize = res.get('content-length') | ||
if (responseSize !== undefined) httpReq.responseSize = parseInt(responseSize) | ||
@@ -47,3 +54,3 @@ } | ||
* | ||
* @param {import('express-serve-static-core').Request} req | ||
* @param {Request} req | ||
*/ | ||
@@ -53,23 +60,17 @@ function requestToErrorReportingHttpRequest(req) { | ||
const httpReq = { | ||
url: req.originalUrl, | ||
url: getUrl(req), | ||
method: req.method, | ||
remoteIp: req.ip || (Array.isArray(req.ips) ? req.ips[0] : null) | ||
} | ||
if (!httpReq.remoteIp) { | ||
const headerIps = req.get('x-forwarded-for') | ||
if (headerIps && (!Array.isArray(headerIps) || headerIps.length > 0)) { | ||
httpReq.remoteIp = Array.isArray(headerIps) ? headerIps[0] : headerIps | ||
} else { | ||
delete httpReq.remoteIp | ||
} | ||
} | ||
const remoteIp = getRemoteIp(req) | ||
if (remoteIp !== undefined) httpReq.remoteIp = remoteIp | ||
const userAgent = req.get('user-agent') | ||
const userAgent = getHeader(req, 'user-agent') | ||
if (userAgent !== undefined) httpReq.userAgent = userAgent | ||
const referrer = req.get('referrer') | ||
const referrer = getHeader(req, 'referrer') | ||
if (referrer !== undefined) httpReq.referrer = referrer | ||
if (req.res) httpReq.responseStatusCode = req.res.statusCode | ||
const res = getResponse(req) | ||
if (res) httpReq.responseStatusCode = res.statusCode | ||
@@ -76,0 +77,0 @@ return httpReq |
@@ -35,2 +35,4 @@ const { format, formatWithOptions, inspect } = require('util') | ||
/** @typedef {import('express-serve-static-core').Request | import('next/server').NextRequest} Request */ | ||
class StructuredLogger { | ||
@@ -42,3 +44,3 @@ | ||
* @param {import('../').ServiceContext} serviceContext | ||
* @param {?import('../').Transport} productionTransport | ||
* @param {import('../').Transport?} productionTransport | ||
* @param {{ [key: string]: string }} labels | ||
@@ -71,4 +73,4 @@ */ | ||
* @protected | ||
* @param {import('express-serve-static-core').Request} request | ||
* @param {?import('../').ExtractUser} extractUser | ||
* @param {Request} request | ||
* @param {import('../').ExtractUser?} extractUser | ||
*/ | ||
@@ -214,2 +216,10 @@ _requestChild(request, extractUser) { | ||
/** @param {any[]} args */ | ||
trace(...args) { | ||
const now = new Date() | ||
const trace = { name: args.length === 0 ? 'Trace' : '' } | ||
Error.captureStackTrace(trace, this.trace) | ||
this._writeFormatted('DEFAULT', [...args, trace.stack], now) | ||
} | ||
/** | ||
@@ -380,2 +390,3 @@ * @private | ||
// @ts-expect-error: message not returned | ||
const { message, ...messageData } = (() => { | ||
@@ -484,4 +495,4 @@ if (typeof data === 'object' && data) { | ||
* @param {{ [key: string]: string }} labels | ||
* @param {import('express-serve-static-core').Request} request | ||
* @param {?import('../').ExtractUser} extractUser | ||
* @param {Request} request | ||
* @param {import('../').ExtractUser?} extractUser | ||
*/ | ||
@@ -488,0 +499,0 @@ constructor(projectId, logName, serviceContext, productionTransport, labels, request, extractUser) { |
@@ -0,1 +1,3 @@ | ||
const { getHeader } = require('./request-properties') | ||
/** Header that carries span context across Google infrastructure. */ | ||
@@ -7,19 +9,21 @@ const TRACE_CONTEXT_HEADER_NAME = 'x-cloud-trace-context' | ||
* @param {string} projectId | ||
* @param {import('express-serve-static-core').Request} req | ||
* @param {import('./StructuredLogger').Request} req | ||
* @returns {{} | { trace: string, spanId: string }} | ||
*/ | ||
module.exports = function extractTraceContext(projectId, req) { | ||
const traceContextHeader = req.get(TRACE_CONTEXT_HEADER_NAME) | ||
const matches = TRACE_CONTEXT_HEADER_FORMAT.exec(traceContextHeader) | ||
if (matches && matches.length === 4 && matches[0] === traceContextHeader) { | ||
try { | ||
return { | ||
/** `projects/<PROJECT-ID>/traces/<TRACE-ID>` */ | ||
trace: `projects/${projectId}/traces/${matches[1]}`, | ||
// Convert spanId to hex and ensure its always a length-16 hex string | ||
spanId: BigInt(matches[2]).toString(16).padStart(16, '0'), | ||
} | ||
} /* c8 ignore next */ catch { /* Bad span number */ } | ||
const traceContextHeader = getHeader(req, TRACE_CONTEXT_HEADER_NAME) | ||
if (traceContextHeader !== null) { | ||
const matches = TRACE_CONTEXT_HEADER_FORMAT.exec(traceContextHeader) | ||
if (matches && matches.length === 4 && matches[0] === traceContextHeader) { | ||
try { | ||
return { | ||
/** `projects/<PROJECT-ID>/traces/<TRACE-ID>` */ | ||
trace: `projects/${projectId}/traces/${matches[1]}`, | ||
// Convert spanId to hex and ensure its always a length-16 hex string | ||
spanId: BigInt(matches[2]).toString(16).padStart(16, '0'), | ||
} | ||
} /* c8 ignore next */ catch { /* Bad span number */ } | ||
} | ||
} | ||
return {} | ||
} |
40999
7.43%11
10%930
8.14%128
17.43%9
12.5%