Skip to content

Commit

Permalink
Merge pull request #891 from myrotvorets/wip
Browse files Browse the repository at this point in the history
Add more observability; remove docker healthcheck
  • Loading branch information
myrotvorets-team authored Oct 9, 2023
2 parents 120956a + 3b56ae0 commit 939499b
Show file tree
Hide file tree
Showing 15 changed files with 105 additions and 20 deletions.
2 changes: 0 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ RUN \
install -d -o nobody -g nobody /usr/share/GeoIP && \
wget https://cdn.myrotvorets.center/m/geoip/GeoIP2-City.mmdb.enc?_=20220530 -U "Mozilla/5.0" -O /usr/share/GeoIP/GeoIP2-City.mmdb.enc && \
wget https://cdn.myrotvorets.center/m/geoip/GeoIP2-ISP.mmdb.enc?_=20220530 -U "Mozilla/5.0" -O /usr/share/GeoIP/GeoIP2-ISP.mmdb.enc
COPY healthcheck.sh entrypoint.sh /usr/local/bin/
HEALTHCHECK --interval=60s --timeout=10s --start-period=5s --retries=3 CMD ["/usr/local/bin/healthcheck.sh"]
USER nobody:nobody
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
COPY --chown=nobody:nobody ./src/specs ./specs
Expand Down
3 changes: 0 additions & 3 deletions healthcheck.sh

This file was deleted.

9 changes: 9 additions & 0 deletions mocha.setup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ import { reset } from 'testdouble';

use(chaiAsPromised);

const env = { ...process.env };
process.env = {
NODE_ENV: 'test',
OTEL_SDK_DISABLED: 'true',
};

/** @type {import('mocha').RootHookObject} */
export const mochaHooks = {
/** @returns {void} */
afterEach() {
reset();
},
afterAll() {
process.env = { ...env };
},
};
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@myrotvorets/express-request-logger": "^1.1.0",
"@myrotvorets/oav-installer": "^4.1.0",
"@myrotvorets/opentelemetry-configurator": "^6.4.2",
"@opentelemetry/api": "^1.6.0",
"awilix": "^8.0.1",
"envalid": "^8.0.0",
"express": "^4.18.2",
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/geoip.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type NextFunction, type Request, type Response, Router } from 'express';
import { type GeoResponse } from '../services/geoip.mjs';
import { type LocalsWithContainer } from '../lib/container.mjs';
import type { GeoResponse } from '../services/geoip.mjs';
import type { LocalsWithContainer } from '../lib/container.mjs';

interface GeolocateParams {
ip: string;
Expand Down
14 changes: 10 additions & 4 deletions src/lib/container.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface Container {
geoIPService: GeoIPService;
environment: ReturnType<typeof environment>;
logger: ReturnType<(typeof configurator)['logger']>;
meter: ReturnType<(typeof configurator)['meter']>;
}

export interface RequestContainer {
Expand All @@ -29,10 +30,14 @@ function createLogger({ req }: RequestContainer): ReturnType<(typeof configurato
return logger;
}

function createGeoIPService({ environment: env }: Container): GeoIPService {
const service = new GeoIPService();
service.setCityDatabase(env.GEOIP_CITY_FILE);
service.setISPDatabase(env.GEOIP_ISP_FILE);
function createMeter(): ReturnType<(typeof configurator)['meter']> {
return configurator.meter();
}

function createGeoIPService({ environment, meter }: Container): GeoIPService {
const service = new GeoIPService({ meter });
service.setCityDatabase(environment.GEOIP_CITY_FILE);
service.setISPDatabase(environment.GEOIP_ISP_FILE);
return service;
}

Expand All @@ -43,6 +48,7 @@ export function initializeContainer(): typeof container {
geoIPService: asFunction(createGeoIPService).singleton(),
environment: asFunction(createEnvironment).singleton(),
logger: asFunction(createLogger).scoped(),
meter: asFunction(createMeter).singleton(),
});

return container;
Expand Down
3 changes: 0 additions & 3 deletions src/lib/otel.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ if (!+(process.env.ENABLE_TRACING || 0)) {
process.env.OTEL_SDK_DISABLED = 'true';
}

process.env.OTEL_LOG_LEVEL = 'info';
process.env.OTEL_METRICS_EXPORTER = 'otlp';

export const configurator = new OpenTelemetryConfigurator({
serviceName: 'psb-api-ipgeo',
instrumentations: [...getExpressInstrumentations(), getFsInstrumentation(true)],
Expand Down
9 changes: 9 additions & 0 deletions src/lib/utils.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { hrtime } from 'node:process';

export function observe<TArgs extends unknown[]>(what: (...args: TArgs) => void, ...args: TArgs): number {
const start = hrtime.bigint();
what(...args);
const end = hrtime.bigint();

return Number((end - start) / 1000n);
}
2 changes: 1 addition & 1 deletion src/middleware/logger.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { requestLogger } from '@myrotvorets/express-request-logger';
import { type LocalsWithContainer } from '../lib/container.mjs';
import type { LocalsWithContainer } from '../lib/container.mjs';

export const loggerMiddleware = requestLogger<never, never, never, never, LocalsWithContainer>({
format: '[PSBAPI-ipgeo] :req[X-Request-ID]\t:method\t:url\t:status :res[content-length]\t:date[iso]\t:total-time',
Expand Down
49 changes: 47 additions & 2 deletions src/services/geoip.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { readFileSync } from 'node:fs';
import { type Histogram, type Meter, ValueType } from '@opentelemetry/api';
import { type CityResponse, type IspResponse, Reader, type Response } from 'maxmind';
import { observe } from '../lib/utils.mjs';

export interface GeoCityResponse {
cc: string | null;
Expand All @@ -22,10 +24,35 @@ export interface GeoPrefixes {

export type GeoResponse = GeoCityResponse & GeoIspResponse & GeoPrefixes;

type ConstructorParams = {
meter: Meter;
};

export class GeoIPService {
private _city: Reader<CityResponse> | undefined;
private _isp: Reader<IspResponse> | undefined;

private static _cityLookupHistogram: Histogram;
private static _ispLookupHistogram: Histogram;

public constructor({ meter }: ConstructorParams) {
if (!GeoIPService._cityLookupHistogram) {
GeoIPService._cityLookupHistogram = meter.createHistogram('geolocate.city.duration', {
description: 'Measures the duration of city lookups.',
unit: 'us',
valueType: ValueType.DOUBLE,
});
}

if (!GeoIPService._ispLookupHistogram) {
GeoIPService._ispLookupHistogram = meter.createHistogram('geolocate.isp.duration', {
description: 'Measures the duration of ISP lookups.',
unit: 'us',
valueType: ValueType.DOUBLE,
});
}
}

public setCityDatabase(file: string): boolean {
this._city = GeoIPService.readDatabaseSync(file);
return this._city !== undefined;
Expand All @@ -50,8 +77,26 @@ export class GeoIPService {
}

public geolocate(ip: string): GeoResponse {
const [city, cprefix] = this._city ? this._city.getWithPrefixLength(ip) : [{} as CityResponse, 0];
const [isp, iprefix] = this._isp ? this._isp.getWithPrefixLength(ip) : [{} as IspResponse, 0];
let city: CityResponse | null = null;
let isp: IspResponse | null = null;
let cprefix = 0;
let iprefix = 0;

if (this._city) {
GeoIPService._cityLookupHistogram.record(
observe((reader: Reader<CityResponse>) => {
[city, cprefix] = reader.getWithPrefixLength(ip);
}, this._city),
);
}

if (this._isp) {
GeoIPService._ispLookupHistogram.record(
observe((reader: Reader<IspResponse>) => {
[isp, iprefix] = reader.getWithPrefixLength(ip);
}, this._isp),
);
}

return {
cprefix,
Expand Down
2 changes: 1 addition & 1 deletion test/functional/controller/geoip.test.mts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { expect } from 'chai';
import { type Express } from 'express';
import type { Express } from 'express';
import request from 'supertest';
import type { ErrorResponse } from '@myrotvorets/express-microservice-middlewares';
import { configureApp, createApp } from '../../../src/server.mjs';
Expand Down
2 changes: 1 addition & 1 deletion test/functional/controller/monitoring.test.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai';
import { type Express } from 'express';
import type { Express } from 'express';
import request from 'supertest';
import type { HealthChecker } from '@cloudnative/health-connect';
import { healthChecker, monitoringController } from '../../../src/controllers/monitoring.mjs';
Expand Down
19 changes: 19 additions & 0 deletions test/unit/lib/utils.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect } from 'chai';
import { observe } from '../../../src/lib/utils.mjs';

describe('utils', function () {
describe('observe', function () {
it('should call the provided function with the provided arguments', function () {
const args = ['hello', 42, true];
let called = false;
const fn = (...fnArgs: typeof args): void => {
expect(fnArgs).to.deep.equal(args);
called = true;
};

const result = observe(fn, ...args);
expect(result).to.be.a('number');
expect(called).to.be.true;
});
});
});
5 changes: 4 additions & 1 deletion test/unit/services/geoip.test.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TestDouble, func, matchers, replaceEsm, when } from 'testdouble';
import { expect } from 'chai';
import type { Reader } from 'maxmind';
import { metrics } from '@opentelemetry/api';
import type { GeoIPService } from '../../../src/services/geoip.mjs';
import {
cityResponseWithCountry,
Expand Down Expand Up @@ -44,7 +45,9 @@ describe('GeoIPService', function () {
when(readFileSyncMock(matchers.isA(String) as string)).thenReturn(Buffer.from(''));

geoip = await import('../../../src/services/geoip.mjs');
service = new geoip.GeoIPService();
service = new geoip.GeoIPService({
meter: metrics.getMeter('test'),
});
});

describe('setCityDatabase()', function () {
Expand Down

0 comments on commit 939499b

Please sign in to comment.