Skip to content
This repository was archived by the owner on Mar 11, 2026. It is now read-only.

Commit 8fb1ba8

Browse files
committed
wip: add express middleware
1 parent e008e83 commit 8fb1ba8

5 files changed

Lines changed: 169 additions & 3 deletions

File tree

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"@google-cloud/paginator": "^0.1.0",
7474
"@google-cloud/projectify": "^0.3.0",
7575
"@google-cloud/promisify": "^0.3.0",
76+
"@opencensus/propagation-stackdriver": "0.0.4",
7677
"arrify": "^1.0.1",
7778
"eventid": "^0.1.2",
7879
"extend": "^3.0.2",
@@ -82,6 +83,7 @@
8283
"google-proto-files": "^0.17.0",
8384
"is": "^3.2.1",
8485
"lodash.merge": "^4.6.1",
86+
"on-finished": "^2.3.0",
8587
"protobufjs": "^6.8.8",
8688
"pumpify": "^1.5.1",
8789
"snakecase-keys": "^1.2.0",
@@ -93,13 +95,16 @@
9395
"@google-cloud/nodejs-repo-tools": "^2.3.3",
9496
"@google-cloud/pubsub": "^0.20.1",
9597
"@google-cloud/storage": "^2.0.2",
98+
"@types/express": "^4.16.0",
99+
"@types/on-finished": "^2.3.1",
96100
"async": "^2.6.1",
97101
"bignumber.js": "^7.2.1",
98102
"codecov": "^3.0.4",
99103
"eslint": "^5.4.0",
100104
"eslint-config-prettier": "^3.0.1",
101105
"eslint-plugin-node": "^7.0.1",
102106
"eslint-plugin-prettier": "^3.0.0",
107+
"gts": "^0.8.0",
103108
"ink-docstrap": "git+https://github.com/docstrap/docstrap.git",
104109
"intelli-espower-loader": "^1.0.1",
105110
"jsdoc": "^3.5.5",
@@ -110,8 +115,7 @@
110115
"prettier": "^1.14.2",
111116
"propprop": "^0.3.1",
112117
"proxyquire": "^2.1.0",
113-
"uuid": "^3.3.2",
114-
"gts": "^0.8.0",
115-
"typescript": "~3.1.0"
118+
"typescript": "~3.1.0",
119+
"uuid": "^3.3.2"
116120
}
117121
}

src/http-request.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*!
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export interface HttpRequest {
18+
requestMethod?: string;
19+
requestUrl?: string;
20+
requestSize?: number;
21+
status?: number;
22+
responseSize?: number;
23+
userAgent?: string;
24+
remoteIp?: string;
25+
serverIp?: string;
26+
referer?: string;
27+
latency?: {
28+
seconds: number;
29+
nanos: number;
30+
};
31+
cacheLookup?: boolean;
32+
cacheHit?: boolean;
33+
cacheValidatedWithOriginServer?: boolean;
34+
cacheFillBytes?: number;
35+
protocol?: string;
36+
}

src/middleware/context.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*!
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as context from '@opencensus/propagation-stackdriver';
18+
19+
type HeaderWrapper = context.HeaderGetter & context.HeaderSetter;
20+
21+
export function getOrInjectContext(headerWrapper: HeaderWrapper): context.SpanContext {
22+
let spanContext = context.extract(headerWrapper);
23+
if (spanContext) {
24+
return spanContext;
25+
}
26+
27+
// We were the first actor to detect lack of context. Establish context.
28+
spanContext = context.generate();
29+
context.inject(headerWrapper, spanContext);
30+
return spanContext;
31+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*!
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { HttpRequest } from '../../http-request';
18+
// Types-only import.
19+
import * as express from 'express';
20+
21+
export function makeHttpRequestData(req: express.Request, res: express.Response, latencyMilliseconds: number): HttpRequest {
22+
return {
23+
status: res.statusCode,
24+
requestUrl: req.url,
25+
requestMethod: req.method,
26+
userAgent: req.headers['user-agent'],
27+
responseSize: Number(res.getHeader('Content-Length')) || 0,
28+
latency: {
29+
seconds: Math.floor(latencyMilliseconds / 1e3),
30+
nanos: Math.floor((latencyMilliseconds % 1e3) * 1e6)
31+
}
32+
};
33+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*!
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import onFinished = require('on-finished');
18+
import {getOrInjectContext} from '../context';
19+
// Types-only import.
20+
import * as express from 'express';
21+
import { makeHttpRequestData } from './make-http-request';
22+
23+
const CHILD_LOG_NAME_SUFFIX = 'applog';
24+
25+
export interface AnnotatedRequestType<LoggerType> extends express.Request {
26+
log: LoggerType;
27+
}
28+
29+
export function makeMiddleware<LoggerType>(projectId: string,
30+
emitRequestLog: (httpRequest: HttpRequest, trace: string) => void,
31+
makeChildLogger: (childSuffix: string, trace: string) => LoggerType) {
32+
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
33+
// TODO(ofrobots): use high-resolution timer.
34+
const requestStartMs = Date.now();
35+
36+
const wrapper = {
37+
setHeader(name: string, value: string) {
38+
req.headers[name] = value;
39+
},
40+
getHeader(name: string) {
41+
return req.headers[name];
42+
}
43+
};
44+
45+
const spanContext = getOrInjectContext(wrapper);
46+
const trace = `projects/${projectId}/traces/${spanContext.traceId}`;
47+
48+
// Install a child logger on the request object.
49+
(req as AnnotatedRequestType<LoggerType>).log = makeChildLogger(CHILD_LOG_NAME_SUFFIX, trace);
50+
51+
// Emit a 'Request Log' on the parent logger.
52+
onFinished(res, () => {
53+
const latencyMs = Date.now() - requestStartMs;
54+
const httpRequest = makeHttpRequestData(req, res, latencyMs);
55+
emitRequestLog(httpRequest, trace);
56+
});
57+
58+
next();
59+
};
60+
}
61+
62+

0 commit comments

Comments
 (0)