Skip to content

Commit

Permalink
stdlib: cache environment variables (#454)
Browse files Browse the repository at this point in the history
Removes the 'log' export from 'insight'.

Accessing process.env.XXX is relatively slow in Node.js.
Benchmark of a plain object property access and accessing process.env.NODE_ENV:

```
property access       500000000  iterations     0  ns/op
process.env access      5000000  iterations   246  ns/op
```

See this thread nodejs/node#3104 for some more context
  • Loading branch information
dirkdev98 authored Oct 29, 2020
1 parent d96c980 commit 4576811
Show file tree
Hide file tree
Showing 26 changed files with 239 additions and 175 deletions.
1 change: 0 additions & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ Let's start with the imports:

```javascript
import { App, generators, loadFromRemote } from "@lbu/code-gen";
import { log } from "@lbu/insight";
import { mainFn } from "@lbu/stdlib";
```

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/scripts/lint.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mainFn, spawn } from "@lbu/stdlib";
import { mainFn, spawn, environment } from "@lbu/stdlib";

mainFn(import.meta, async () => {
const { exitCode: lint } = await spawn("./node_modules/.bin/eslint", [
Expand All @@ -9,7 +9,7 @@ mainFn(import.meta, async () => {
]);

const prettierCommand =
process.env.CI === "true" ? ["--check"] : ["--write", "--list-different"];
environment.CI === "true" ? ["--check"] : ["--write", "--list-different"];

const { exitCode: pretty } = await spawn("./node_modules/.bin/prettier", [
...prettierCommand,
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/benchmarking/printer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { writeFileSync } from "fs";
import { inspect } from "util";
import { AppError, isNil, pathJoin } from "@lbu/stdlib";
import { AppError, environment, isNil, pathJoin } from "@lbu/stdlib";
import { benchLogger, state } from "./state.js";

export function printBenchResults() {
Expand Down Expand Up @@ -40,7 +40,7 @@ export function printBenchResults() {

logFn(result.join("\n"));

if (process.env.CI === "true") {
if (environment.CI === "true") {
// Write output to a file so it can be used in other actions
// Add some point we may want to do some pretty printing to format as a table or
// something
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/commands/docker.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { exec, spawn } from "@lbu/stdlib";
import { environment, exec, spawn } from "@lbu/stdlib";

const SUB_COMMANDS = ["up", "down", "clean", "reset"];

Expand Down Expand Up @@ -200,7 +200,7 @@ async function resetDatabase(logger, containerInfo) {
return startExitCode;
}

const name = process.env.APP_NAME;
const name = environment.APP_NAME;

logger.info(`Resetting ${name} database`);
const { exitCode: postgresExit } = await spawn(`sh`, [
Expand Down Expand Up @@ -253,5 +253,5 @@ async function getKnownContainers() {
}

function getPostgresVersion() {
return Number(process.env.POSTGRES_VERSION ?? "12");
return Number(environment.POSTGRES_VERSION ?? "12");
}
8 changes: 4 additions & 4 deletions packages/cli/src/commands/proxy.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createServer } from "http";
import { isNil } from "@lbu/stdlib";
import { environment, isNil } from "@lbu/stdlib";
import proxy from "http-proxy";

/**
Expand All @@ -8,7 +8,7 @@ import proxy from "http-proxy";
*/
export async function proxyCommand(logger) {
const port = parseInt(
(process.env.API_URL ?? process.env.NEXT_PUBLIC_API_URL ?? "")
(environment.API_URL ?? environment.NEXT_PUBLIC_API_URL ?? "")
.split(":")
.pop(),
);
Expand All @@ -19,7 +19,7 @@ export async function proxyCommand(logger) {
);
process.exit(1);
}
if ((process.env.PROXY_URL ?? "").length === 0) {
if ((environment.PROXY_URL ?? "").length === 0) {
logger.error("Please set the `PROXY_URL` environment variable");
process.exit(1);
}
Expand All @@ -39,7 +39,7 @@ export async function proxyCommand(logger) {

const allowMethods = "GET,PUT,POST,PATCH,DELETE,HEAD,OPTIONS";
const options = {
target: process.env.PROXY_URL,
target: environment.PROXY_URL,
changeOrigin: true,
cookieDomainRewrite: "",
};
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/template/scripts/api.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mainFn, isProduction, isStaging } from "@lbu/stdlib";
import { mainFn, isProduction, isStaging, environment } from "@lbu/stdlib";
import { constructApp } from "../src/api.js";
import { injectServices } from "../src/service.js";
import { app } from "../src/services/index.js";
Expand All @@ -12,7 +12,7 @@ async function main(logger) {
await injectServices();
await constructApp();

const port = process.env.PORT || 3000;
const port = environment.PORT || 3000;
app.listen(port, () => {
logger.info({
msg: "Listening",
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/template/src/service.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { newLogger } from "@lbu/insight";
import { createBodyParsers, session } from "@lbu/server";
import { isStaging } from "@lbu/stdlib";
import { environment, isStaging } from "@lbu/stdlib";
import {
FileCache,
newMinioClient,
Expand Down Expand Up @@ -40,7 +40,7 @@ export async function injectServices() {
createIfNotExists: isStaging(),
}),
);
setAppBucket(process.env.APP_NAME);
setAppBucket(environment.APP_NAME);
setMinio(newMinioClient({}));
setSessionStore(newSessionStore(sql));
setFileCache(new FileCache(sql, minio, appBucket));
Expand Down
11 changes: 7 additions & 4 deletions packages/code-gen/src/open-api-importer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { log } from "@lbu/insight";
import { newLogger } from "@lbu/insight";
import { isNil } from "@lbu/stdlib";
import {
AnyOfType,
Expand Down Expand Up @@ -45,6 +45,7 @@ export function convertOpenAPISpec(defaultGroup, data) {
* openAPIReferences to resolve $ref's in the document
*/
const context = {
logger: newLogger(),
result,
defaultGroup: lowerCaseFirst(defaultGroup),
data,
Expand Down Expand Up @@ -399,7 +400,7 @@ function convertSchema(context, schema) {
result.values = convertSchema(context, schema.additionalProperties);

if (!isNil(schema.minProperties) || !isNil(schema.maxProperties)) {
log.info(
context.logger.info(
"object#minProperties and object#maxProperties are not supported",
);
}
Expand Down Expand Up @@ -451,7 +452,7 @@ function convertSchema(context, schema) {
result.validator.max = schema.maximum;
}
if (!isNil(schema.exclusiveMinimum) || !isNil(schema.exclusiveMaximum)) {
log.info(
context.logger.info(
"number#exclusiveMinimum and number#exclusiveMaximum are not supported",
);
}
Expand Down Expand Up @@ -480,7 +481,9 @@ function convertSchema(context, schema) {
assignBaseData();

if (!schema.$ref.startsWith("#/")) {
log.info(`Only local references supported. Found ${schema.$ref}`);
context.logger.info(
`Only local references supported. Found ${schema.$ref}`,
);
} else {
result.reference = {
group: context.defaultGroup,
Expand Down
5 changes: 0 additions & 5 deletions packages/insight/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,6 @@ export function bytesToHumanReadable(bytes?: number): string;
*/
export function printProcessMemoryUsage(logger: Logger): void;

/**
* Standard log instance
*/
export const log: Logger;

/**
* Basic timing and call information
*/
Expand Down
9 changes: 2 additions & 7 deletions packages/insight/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { newLogger } from "./src/logger/logger.js";
export { newLogger } from "./src/logger/logger.js";

export { bytesToHumanReadable, printProcessMemoryUsage } from "./src/memory.js";
export { newLogger } from "./src/logger/logger.js";

export {
newEvent,
Expand All @@ -11,9 +10,5 @@ export {
newTestEvent,
newEventFromEvent,
} from "./src/events.js";
export { postgresTableSizes } from "./src/postgres.js";

/**
* @type {Logger}
*/
export const log = newLogger({});
export { postgresTableSizes } from "./src/postgres.js";
3 changes: 3 additions & 0 deletions packages/insight/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"logger"
],
"license": "MIT",
"dependencies": {
"@lbu/stdlib": "0.0.91"
},
"maintainers": [
{
"name": "Dirk de Visser",
Expand Down
5 changes: 3 additions & 2 deletions packages/insight/src/logger/logger.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { environment } from "@lbu/stdlib";
import { writeNDJSON, writePretty } from "./writer.js";

/**
* @param {LoggerOptions} [options]
* @returns {Logger}
*/
export function newLogger(options) {
const app = process.env.APP_NAME;
const app = environment.APP_NAME;
const isProduction =
options?.pretty === false || process.env.NODE_ENV === "production";
options?.pretty === false || environment.NODE_ENV === "production";
const stream = options?.stream ?? process.stdout;

const logFn = isProduction
Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/middleware/cors.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isStaging } from "@lbu/stdlib";
import { environment, isStaging } from "@lbu/stdlib";

/*
Original copy from: https://github.com/zadzbw/koa2-cors/commit/45b6de0de6c4816b93d49335490b81995d450191
Expand Down Expand Up @@ -62,11 +62,11 @@ export function cors(options = {}) {
if (typeof options.origin === "function") {
originFn = options.origin;
} else if (
process.env.CORS_URL !== undefined &&
process.env.CORS_URL.length > 0
environment.CORS_URL !== undefined &&
environment.CORS_URL.length > 0
) {
// Use CORS_URL array provided via environment variables
const allowedOrigins = (process.env.CORS_URL || "").split(",");
const allowedOrigins = (environment.CORS_URL || "").split(",");
const localhostRegex = /^http:\/\/localhost:\d{1,6}$/gi;

if (isStaging()) {
Expand Down
12 changes: 6 additions & 6 deletions packages/server/src/middleware/session.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isNil, isProduction, merge, uuid } from "@lbu/stdlib";
import { environment, isNil, isProduction, merge, uuid } from "@lbu/stdlib";
import KeyGrip from "keygrip";
import koaSession from "koa-session";

Expand All @@ -17,11 +17,11 @@ export function session(app, opts) {
const options = merge(
{},
{
key: `${process.env.APP_NAME.toLowerCase()}.sess`,
key: `${environment.APP_NAME.toLowerCase()}.sess`,
maxAge: 6 * 24 * 60 * 60 * 1000,
renew: true,
secure: isProduction(),
domain: !isProduction() ? undefined : process.env.COOKIE_URL,
domain: !isProduction() ? undefined : environment.COOKIE_URL,
sameSite: "lax",
overwrite: true,
httpOnly: true,
Expand All @@ -44,14 +44,14 @@ export function session(app, opts) {
*/
function getKeys() {
if (!isProduction()) {
return [process.env.APP_NAME];
return [environment.APP_NAME];
}

if (isNil(process.env.APP_KEYS) || process.env.APP_KEYS.length < 20) {
if (isNil(environment.APP_KEYS) || environment.APP_KEYS.length < 20) {
throw new Error("Missing APP_KEYS in environment or generate a longer key");
}

const keys = process.env.APP_KEYS.split(",");
const keys = environment.APP_KEYS.split(",");
return new KeyGrip(keys, "sha256");
}

Expand Down
21 changes: 21 additions & 0 deletions packages/stdlib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,27 @@ export function filenameForModule(meta: ImportMeta): string;
*/
export function dirnameForModule(meta: ImportMeta): string;

/**
* Cached environment, set by `refreshEnvironmentCache()`
*/
export const environment: typeof process.env;

/**
* Repopulate the cached environment copy.
* This should only be necessary when you or a sub package mutates the environment.
* The `mainFn` / `mainTestFn` / `mainBenchFn` / ... will call this function by default
* after loading your `.env` file.
*
* Accessing process.env.XXX is relatively slow in Node.js.
* Benchmark of a plain object property access and accessing process.env.NODE_ENV:
*
* property access 500000000 iterations 0 ns/op
* process.env access 5000000 iterations 246 ns/op
*
* See this thread: https://github.com/nodejs/node/issues/3104
*/
export function refreshEnvironmentCache(): void;

/**
* Returns whether NODE_ENV === "production"
*/
Expand Down
13 changes: 11 additions & 2 deletions packages/stdlib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
export { uuid } from "./src/datatypes.js";

export { AppError } from "./src/error.js";

export {
isProduction,
isStaging,
environment,
refreshEnvironmentCache,
} from "./src/env.js";

export {
isNil,
isPlainObject,
Expand All @@ -8,6 +17,7 @@ export {
unFlatten,
camelToSnakeCase,
} from "./src/lodash.js";

export {
exec,
spawn,
Expand All @@ -16,13 +26,12 @@ export {
processDirectoryRecursive,
processDirectoryRecursiveSync,
} from "./src/node.js";

export {
getSecondsSinceEpoch,
gc,
mainFn,
noop,
filenameForModule,
dirnameForModule,
isProduction,
isStaging,
} from "./src/utils.js";
38 changes: 38 additions & 0 deletions packages/stdlib/src/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Cached process.env
*/
export let environment = {};

/**
* Repopulate the cached environment copy.
* This should only be necessary when you or a sub package mutates the environment.
* The `mainFn` / `mainTestFn` / `mainBenchFn` / ... will call this function by default
* after loading your `.env` file.
*
* Accessing process.env.XXX is relatively in Node.js.
* Benchmark of a plain object property access and accessing process.env.NODE_ENV:
*
* property access 500000000 iterations 0 ns/op
* process.env access 5000000 iterations 246 ns/op
*
* See this thread: https://github.com/nodejs/node/issues/3104
*/
export function refreshEnvironmentCache() {
environment = JSON.parse(JSON.stringify(process.env));
}

/**
* @returns {boolean}
*/
export function isProduction() {
return environment.NODE_ENV === "production";
}

/**
* @returns {boolean}
*/
export function isStaging() {
return (
environment.NODE_ENV !== "production" || environment.IS_STAGING === "true"
);
}
Loading

0 comments on commit 4576811

Please sign in to comment.