* chore: squash commit * fix: tweak some mentioned issues --------- Co-authored-by: incubator4 <aries0robin@gmail.com>
This commit is contained in:
parent
a761fd0857
commit
68c7430fbb
|
|
@ -12,6 +12,7 @@ import debug from '@/middleware/debug';
|
|||
import header from '@/middleware/header';
|
||||
import antiHotlink from '@/middleware/anti-hotlink';
|
||||
import parameter from '@/middleware/parameter';
|
||||
import trace from '@/middleware/trace';
|
||||
import { jsxRenderer } from 'hono/jsx-renderer';
|
||||
import { trimTrailingSlash } from 'hono/trailing-slash';
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ app.use(
|
|||
})
|
||||
);
|
||||
app.use(mLogger);
|
||||
app.use(trace);
|
||||
app.use(sentry);
|
||||
app.use(accessControl);
|
||||
app.use(debug);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import Error from '@/views/error';
|
|||
|
||||
import NotFoundError from './types/not-found';
|
||||
|
||||
import { requestMetric } from '@/utils/otel';
|
||||
|
||||
export const errorHandler: ErrorHandler = (error, ctx) => {
|
||||
const requestPath = ctx.req.path;
|
||||
const matchedRoute = ctx.req.routePath;
|
||||
|
|
@ -61,6 +63,7 @@ export const errorHandler: ErrorHandler = (error, ctx) => {
|
|||
const message = `${error.name}: ${errorMessage}`;
|
||||
|
||||
logger.error(`Error in ${requestPath}: ${message}`);
|
||||
requestMetric.error({ path: requestPath, method: ctx.req.method, status: ctx.res.status });
|
||||
|
||||
return config.isPackage
|
||||
? ctx.json({
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { requestMetric } from '@/utils/otel';
|
||||
import { MiddlewareHandler } from 'hono';
|
||||
import logger from '@/utils/logger';
|
||||
import { getPath, time } from '@/utils/helpers';
|
||||
|
|
@ -34,7 +35,10 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
|
|||
|
||||
await next();
|
||||
|
||||
logger.info(`${LogPrefix.Outgoing} ${method} ${path} ${colorStatus(ctx.res.status)} ${time(start)}`);
|
||||
const status = ctx.res.status;
|
||||
|
||||
logger.info(`${LogPrefix.Outgoing} ${method} ${path} ${colorStatus(status)} ${time(start)}`);
|
||||
requestMetric.success(Date.now() - start, { path, method, status });
|
||||
};
|
||||
|
||||
export default middleware;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { MiddlewareHandler } from 'hono';
|
||||
import { getPath } from '@/utils/helpers';
|
||||
import { config } from '@/config';
|
||||
import { tracer } from '@/utils/otel';
|
||||
|
||||
const middleware: MiddlewareHandler = async (ctx, next) => {
|
||||
if (config.debugInfo) {
|
||||
// Only enable tracing in debug mode
|
||||
const { method, raw } = ctx.req;
|
||||
const path = getPath(raw);
|
||||
|
||||
const span = tracer.startSpan(`${method} ${path}`, {
|
||||
kind: 1, // server
|
||||
attributes: {},
|
||||
});
|
||||
span.addEvent('invoking handleRequest');
|
||||
await next();
|
||||
span.end();
|
||||
} else {
|
||||
// Skip
|
||||
await next();
|
||||
}
|
||||
};
|
||||
|
||||
export default middleware;
|
||||
|
|
@ -4,9 +4,11 @@ import { Hono, type Handler } from 'hono';
|
|||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { serveStatic } from '@hono/node-server/serve-static';
|
||||
import { config } from '@/config';
|
||||
|
||||
import index from '@/routes/index';
|
||||
import robotstxt from '@/routes/robots.txt';
|
||||
import metrics from '@/routes/metrics';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
|
|
@ -99,6 +101,10 @@ for (const namespace in namespaces) {
|
|||
|
||||
app.get('/', index);
|
||||
app.get('/robots.txt', robotstxt);
|
||||
if (config.debugInfo) {
|
||||
// Only enable tracing in debug mode
|
||||
app.get('/metrics', metrics);
|
||||
}
|
||||
app.use(
|
||||
'/*',
|
||||
serveStatic({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import type { Handler } from 'hono';
|
||||
import { getContext } from '@/utils/otel';
|
||||
|
||||
const handler: Handler = (ctx) =>
|
||||
getContext()
|
||||
.then((val) => ctx.text(val))
|
||||
.catch((error) => {
|
||||
ctx.status(500);
|
||||
ctx.json({ error });
|
||||
});
|
||||
|
||||
export default handler;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from './metric';
|
||||
export * from './trace';
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { Resource } from '@opentelemetry/resources';
|
||||
import { PrometheusExporter, PrometheusSerializer } from '@opentelemetry/exporter-prometheus';
|
||||
import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
||||
import { MeterProvider } from '@opentelemetry/sdk-metrics';
|
||||
import { Attributes } from '@opentelemetry/api';
|
||||
|
||||
interface IMetricAttributes extends Attributes {
|
||||
method: string;
|
||||
path: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
interface IHistogramAttributes extends IMetricAttributes {
|
||||
unit: string;
|
||||
}
|
||||
|
||||
const METRIC_PREFIX = 'rsshub';
|
||||
|
||||
const exporter = new PrometheusExporter({});
|
||||
|
||||
const provider = new MeterProvider({
|
||||
resource: new Resource({
|
||||
[SEMRESATTRS_SERVICE_NAME]: 'rsshub',
|
||||
}),
|
||||
readers: [exporter],
|
||||
});
|
||||
|
||||
const serializer = new PrometheusSerializer();
|
||||
|
||||
const meter = provider.getMeter('rsshub');
|
||||
|
||||
const requestTotal = meter.createCounter<IMetricAttributes>(`${METRIC_PREFIX}_request_total`);
|
||||
const requestErrorTotal = meter.createCounter<IMetricAttributes>(`${METRIC_PREFIX}_request_error_total`);
|
||||
const requestDurationSecondsBucket = meter.createHistogram<IHistogramAttributes>(`${METRIC_PREFIX}_request_duration_seconds_bucket`, {
|
||||
advice: {
|
||||
explicitBucketBoundaries: [0.01, 0.1, 1, 2, 5, 15, 30, 60],
|
||||
},
|
||||
});
|
||||
const request_duration_milliseconds_bucket = meter.createHistogram<IHistogramAttributes>(`${METRIC_PREFIX}_request_duration_milliseconds_bucket`, {
|
||||
advice: {
|
||||
explicitBucketBoundaries: [10, 20, 50, 100, 250, 500, 1000, 5000, 15000],
|
||||
},
|
||||
});
|
||||
|
||||
export const requestMetric = {
|
||||
success: (value: number, attributes: IMetricAttributes) => {
|
||||
requestTotal.add(1, attributes);
|
||||
request_duration_milliseconds_bucket.record(value, { unit: 'millisecond', ...attributes });
|
||||
requestDurationSecondsBucket.record(value / 1000, { unit: 'second', ...attributes });
|
||||
},
|
||||
error: (attributes: IMetricAttributes) => {
|
||||
requestErrorTotal.add(1, attributes);
|
||||
},
|
||||
};
|
||||
|
||||
export const getContext = () =>
|
||||
new Promise<string>((resolve, reject) => {
|
||||
exporter
|
||||
.collect()
|
||||
.then((value) => {
|
||||
resolve(serializer.serialize(value.resourceMetrics));
|
||||
})
|
||||
.finally(() => {
|
||||
reject('');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { Resource } from '@opentelemetry/resources';
|
||||
import { BasicTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
||||
import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
||||
|
||||
const provider = new BasicTracerProvider({
|
||||
resource: new Resource({
|
||||
[SEMRESATTRS_SERVICE_NAME]: 'rsshub',
|
||||
}),
|
||||
});
|
||||
|
||||
const exporter = new OTLPTraceExporter({
|
||||
// optional OTEL_EXPORTER_OTLP_ENDPOINT=https://localhost:4318
|
||||
});
|
||||
|
||||
provider.addSpanProcessor(
|
||||
new BatchSpanProcessor(exporter, {
|
||||
// The maximum queue size. After the size is reached spans are dropped.
|
||||
maxQueueSize: 4096,
|
||||
// The interval between two consecutive exports
|
||||
scheduledDelayMillis: 30000,
|
||||
})
|
||||
);
|
||||
|
||||
provider.register();
|
||||
|
||||
export const tracer = provider.getTracer('rsshub');
|
||||
export const mainSpan = tracer.startSpan('main');
|
||||
|
|
@ -53,6 +53,13 @@
|
|||
"@hono/node-server": "1.12.0",
|
||||
"@hono/zod-openapi": "0.14.7",
|
||||
"@notionhq/client": "2.2.15",
|
||||
"@opentelemetry/api": "1.8.0",
|
||||
"@opentelemetry/exporter-prometheus": "0.49.1",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "0.49.1",
|
||||
"@opentelemetry/resources": "1.22.0",
|
||||
"@opentelemetry/sdk-metrics": "1.22.0",
|
||||
"@opentelemetry/sdk-trace-base": "1.22.0",
|
||||
"@opentelemetry/semantic-conventions": "1.22.0",
|
||||
"@postlight/parser": "2.2.3",
|
||||
"@scalar/hono-api-reference": "0.5.94",
|
||||
"@sentry/node": "7.116.0",
|
||||
|
|
|
|||
159
pnpm-lock.yaml
159
pnpm-lock.yaml
|
|
@ -17,6 +17,27 @@ importers:
|
|||
'@notionhq/client':
|
||||
specifier: 2.2.15
|
||||
version: 2.2.15
|
||||
'@opentelemetry/api':
|
||||
specifier: 1.8.0
|
||||
version: 1.8.0
|
||||
'@opentelemetry/exporter-prometheus':
|
||||
specifier: 0.49.1
|
||||
version: 0.49.1(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/exporter-trace-otlp-http':
|
||||
specifier: 0.49.1
|
||||
version: 0.49.1(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/resources':
|
||||
specifier: 1.22.0
|
||||
version: 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/sdk-metrics':
|
||||
specifier: 1.22.0
|
||||
version: 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/sdk-trace-base':
|
||||
specifier: 1.22.0
|
||||
version: 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/semantic-conventions':
|
||||
specifier: 1.22.0
|
||||
version: 1.22.0
|
||||
'@postlight/parser':
|
||||
specifier: 2.2.3
|
||||
version: 2.2.3
|
||||
|
|
@ -1426,6 +1447,73 @@ packages:
|
|||
'@open-draft/until@2.1.0':
|
||||
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
|
||||
|
||||
'@opentelemetry/api-logs@0.49.1':
|
||||
resolution: {integrity: sha512-kaNl/T7WzyMUQHQlVq7q0oV4Kev6+0xFwqzofryC66jgGMacd0QH5TwfpbUwSTby+SdAdprAe5UKMvBw4tKS5Q==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@opentelemetry/api@1.8.0':
|
||||
resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
'@opentelemetry/core@1.22.0':
|
||||
resolution: {integrity: sha512-0VoAlT6x+Xzik1v9goJ3pZ2ppi6+xd3aUfg4brfrLkDBHRIVjMP0eBHrKrhB+NKcDyMAg8fAbGL3Npg/F6AwWA==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.0.0 <1.9.0'
|
||||
|
||||
'@opentelemetry/exporter-prometheus@0.49.1':
|
||||
resolution: {integrity: sha512-FgzGl6OH22f+Wb1dh/TnoQSnZE2SCADhHx06nMqxivSqRJ9t3AhUdMsEOFt2IMjZClE705pcsLHk10BCJ79vsA==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.3.0
|
||||
|
||||
'@opentelemetry/exporter-trace-otlp-http@0.49.1':
|
||||
resolution: {integrity: sha512-KOLtZfZvIrpGZLVvblKsiVQT7gQUZNKcUUH24Zz6Xbi7LJb9Vt6xtUZFYdR5IIjvt47PIqBKDWUQlU0o1wAsRw==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
|
||||
'@opentelemetry/otlp-exporter-base@0.49.1':
|
||||
resolution: {integrity: sha512-z6sHliPqDgJU45kQatAettY9/eVF58qVPaTuejw9YWfSRqid9pXPYeegDCSdyS47KAUgAtm+nC28K3pfF27HWg==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
|
||||
'@opentelemetry/otlp-transformer@0.49.1':
|
||||
resolution: {integrity: sha512-Z+koA4wp9L9e3jkFacyXTGphSWTbOKjwwXMpb0CxNb0kjTHGUxhYRN8GnkLFsFo5NbZPjP07hwAqeEG/uCratQ==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.3.0 <1.9.0'
|
||||
|
||||
'@opentelemetry/resources@1.22.0':
|
||||
resolution: {integrity: sha512-+vNeIFPH2hfcNL0AJk/ykJXoUCtR1YaDUZM+p3wZNU4Hq98gzq+7b43xbkXjadD9VhWIUQqEwXyY64q6msPj6A==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.0.0 <1.9.0'
|
||||
|
||||
'@opentelemetry/sdk-logs@0.49.1':
|
||||
resolution: {integrity: sha512-gCzYWsJE0h+3cuh3/cK+9UwlVFyHvj3PReIOCDOmdeXOp90ZjKRoDOJBc3mvk1LL6wyl1RWIivR8Rg9OToyesw==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.4.0 <1.9.0'
|
||||
'@opentelemetry/api-logs': '>=0.39.1'
|
||||
|
||||
'@opentelemetry/sdk-metrics@1.22.0':
|
||||
resolution: {integrity: sha512-k6iIx6H3TZ+BVMr2z8M16ri2OxWaljg5h8ihGJxi/KQWcjign6FEaEzuigXt5bK9wVEhqAcWLCfarSftaNWkkg==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.3.0 <1.9.0'
|
||||
|
||||
'@opentelemetry/sdk-trace-base@1.22.0':
|
||||
resolution: {integrity: sha512-pfTuSIpCKONC6vkTpv6VmACxD+P1woZf4q0K46nSUvXFvOFqjBYKFaAMkKD3M1mlKUUh0Oajwj35qNjMl80m1Q==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.0.0 <1.9.0'
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.22.0':
|
||||
resolution: {integrity: sha512-CAOgFOKLybd02uj/GhCdEeeBjOS0yeoDeo/CA7ASBSmenpZHAKGB3iDm/rv3BQLcabb/OprDEsSQ1y0P8A7Siw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@otplib/core@12.0.1':
|
||||
resolution: {integrity: sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==}
|
||||
|
||||
|
|
@ -7824,6 +7912,77 @@ snapshots:
|
|||
|
||||
'@open-draft/until@2.1.0': {}
|
||||
|
||||
'@opentelemetry/api-logs@0.49.1':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
|
||||
'@opentelemetry/api@1.8.0': {}
|
||||
|
||||
'@opentelemetry/core@1.22.0(@opentelemetry/api@1.8.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
'@opentelemetry/semantic-conventions': 1.22.0
|
||||
|
||||
'@opentelemetry/exporter-prometheus@0.49.1(@opentelemetry/api@1.8.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
'@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/sdk-metrics': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
|
||||
'@opentelemetry/exporter-trace-otlp-http@0.49.1(@opentelemetry/api@1.8.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
'@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/otlp-exporter-base': 0.49.1(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/otlp-transformer': 0.49.1(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
|
||||
'@opentelemetry/otlp-exporter-base@0.49.1(@opentelemetry/api@1.8.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
'@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
|
||||
'@opentelemetry/otlp-transformer@0.49.1(@opentelemetry/api@1.8.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
'@opentelemetry/api-logs': 0.49.1
|
||||
'@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/sdk-logs': 0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/sdk-metrics': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
|
||||
'@opentelemetry/resources@1.22.0(@opentelemetry/api@1.8.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
'@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/semantic-conventions': 1.22.0
|
||||
|
||||
'@opentelemetry/sdk-logs@0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.8.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
'@opentelemetry/api-logs': 0.49.1
|
||||
'@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
|
||||
'@opentelemetry/sdk-metrics@1.22.0(@opentelemetry/api@1.8.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
'@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
lodash.merge: 4.6.2
|
||||
|
||||
'@opentelemetry/sdk-trace-base@1.22.0(@opentelemetry/api@1.8.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.8.0
|
||||
'@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0)
|
||||
'@opentelemetry/semantic-conventions': 1.22.0
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.22.0': {}
|
||||
|
||||
'@otplib/core@12.0.1': {}
|
||||
|
||||
'@otplib/plugin-crypto@12.0.1':
|
||||
|
|
|
|||
Loading…
Reference in New Issue