Skip to content

Team logs #1028

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions nais/nais.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ spec:
- application: kabal-innstillinger
- application: klage-kodeverk-api
- application: kabal-text-templates
- application: logging
namespace: nais-system
external:
- host: login.microsoftonline.com
- host: hooks.slack.com
Expand Down Expand Up @@ -82,5 +84,3 @@ spec:
autoInstrumentation:
enabled: true
runtime: nodejs
secureLogs:
enabled: true
9 changes: 8 additions & 1 deletion server/src/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'node:path';
import { isLocal } from '@app/config/env';
import { isLocal, isTest } from '@app/config/env';
import { requiredEnvJson, requiredEnvString } from '@app/config/env-var';
import type { JWK } from 'jose';

Expand Down Expand Up @@ -40,3 +40,10 @@ export const PROXY_VERSION = requiredEnvString('VERSION', defaultValue);
export const PORT = requiredEnvString('PORT', '8080');
export const NAIS_CLUSTER_NAME = requiredEnvString('NAIS_CLUSTER_NAME', defaultValue);
export const START_TIME = Date.now();

export const TEAM_LOG_PARMS = {
google_cloud_project: requiredEnvString('GOOGLE_CLOUD_PROJECT', isTest ? '' : undefined),
nais_namespace_name: requiredEnvString('NAIS_NAMESPACE', isTest ? '' : undefined),
nais_pod_name: requiredEnvString('HOSTNAME', isTest ? '' : undefined),
nais_container_name: requiredEnvString('NAIS_APP_NAME', isTest ? '' : undefined),
};
3 changes: 3 additions & 0 deletions server/src/config/env.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { requiredEnvString } from '@app/config/env-var';
import { serverConfig } from '@app/config/server-config';
import { hasOwn } from '@app/functions/functions';

const getEnvironmentVersion = <T>(local: T, development: T, production: T): T => {
if (isDeployedToDev) {
Expand All @@ -17,6 +18,8 @@ const isDeployedToDev = serverConfig.cluster === 'dev-gcp';
export const isDeployedToProd = serverConfig.cluster === 'prod-gcp';
export const isDeployed = isDeployedToDev || isDeployedToProd;
export const isLocal = !isDeployed;
// biome-ignore lint/nursery/noProcessEnv: Used in test
export const isTest = hasOwn(process.env, 'NODE_ENV') && process.env.NODE_ENV === 'test';

export const ENVIRONMENT = getEnvironmentVersion('local', 'development', 'production');

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion server/src/helpers/token-parser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { hasOwn, isObject } from '@app/plugins/crdt/functions';
import { hasOwn, isObject } from '@app/functions/functions';

interface TokenPayload {
aud: string;
Expand Down
32 changes: 18 additions & 14 deletions server/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { hasOwn } from '@app/plugins/crdt/functions';
import { TEAM_LOG_PARMS } from '@app/config/config';
import { hasOwn } from '@app/functions/functions';
import { isDeployed } from './config/env';

const VERSION: string =
// biome-ignore lint/nursery/noProcessEnv:
hasOwn(process.env, 'VERSION') && typeof process.env.VERSION === 'string' ? process.env.VERSION : 'unknown';

const LOGGERS: Map<string, Logger> = new Map();
const SECURE_LOGGERS: Map<string, Logger> = new Map();
const TEAM_LOGGERS: Map<string, Logger> = new Map();

type SerializableValue =
| string
Expand Down Expand Up @@ -96,7 +97,8 @@ const logDefined = (message: string | undefined, level: Level): void => {
const getLog = (
module: string,
level: Level,
{ msg, trace_id, span_id, client_version, tab_id, error, data }: LogArgs,
{ msg: message, trace_id, span_id, client_version, tab_id, error, data }: LogArgs,
isTeamLog = false,
): string | undefined => {
const log: Log = {
...(typeof data === 'object' && data !== null && !Array.isArray(data) ? data : { data }),
Expand All @@ -112,13 +114,13 @@ const getLog = (

if (error instanceof Error) {
log.stacktrace = error.stack;
log.message = typeof msg === 'string' ? `${msg} - ${error.name}: ${error.message}` : error.message;
log.message = typeof message === 'string' ? `${message} - ${error.name}: ${error.message}` : error.message;
} else {
log.message = msg;
log.message = message;
}

if (isDeployed) {
return JSON.stringify(log);
return JSON.stringify(isTeamLog ? { ...TEAM_LOG_PARMS, ...log, severity: level.toUpperCase() } : log);
}

if (
Expand All @@ -131,22 +133,24 @@ const getLog = (
return;
}

return msg;
return message;
};

export const getSecureLogger = (module: string) => {
const cachedLogger = SECURE_LOGGERS.get(module);
export const getTeamLogger = (module: string) => {
const cachedLogger = TEAM_LOGGERS.get(module);

if (typeof cachedLogger !== 'undefined') {
return cachedLogger;
}

const sendLog = (level: Level, args: LogArgs) => {
const log = getLog(module, level, args);

return isDeployed
? fetch('http://localhost:19880', { method: 'POST', body: log, headers: { 'Content-Type': 'application/json' } })
: logDefined(log, level);
? fetch('http://team-logs.nais-system', {
method: 'POST',
body: getLog(module, level, args, true),
headers: { 'Content-Type': 'application/json' },
})
: logDefined(getLog(module, level, args), level);
};

const logger: Logger = {
Expand All @@ -156,7 +160,7 @@ export const getSecureLogger = (module: string) => {
error: (args) => sendLog('error', args),
};

SECURE_LOGGERS.set(module, logger);
TEAM_LOGGERS.set(module, logger);

return logger;
};
2 changes: 1 addition & 1 deletion server/src/plugins/crdt/api/get-document.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getCacheKey, oboCache } from '@app/auth/cache/cache';
import { ApiClientEnum } from '@app/config/config';
import { isObject } from '@app/functions/functions';
import { generateTraceparent } from '@app/helpers/traceparent';
import { getLogger } from '@app/logger';
import { KABAL_API_URL } from '@app/plugins/crdt/api/url';
import type { ConnectionContext } from '@app/plugins/crdt/context';
import { isObject } from '@app/plugins/crdt/functions';
import { slateNodesToInsertDelta } from '@slate-yjs/core';
import type { Node } from 'slate';
import { Doc, XmlText, encodeStateAsUpdateV2 } from 'yjs';
Expand Down
8 changes: 4 additions & 4 deletions server/src/plugins/crdt/collaboration-server.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { getCacheKey, oboCache } from '@app/auth/cache/cache';
import { ApiClientEnum } from '@app/config/config';
import { isDeployed } from '@app/config/env';
import { hasOwn, isObject } from '@app/functions/functions';
import { isNotNull } from '@app/functions/guards';
import { parseTokenPayload } from '@app/helpers/token-parser';
import { type Level, getLogger, getSecureLogger } from '@app/logger';
import { type Level, getLogger, getTeamLogger } from '@app/logger';
import { getDocument } from '@app/plugins/crdt/api/get-document';
import { getDocumentJson, setDocument } from '@app/plugins/crdt/api/set-document';
import { type ConnectionContext, isConnectionContext } from '@app/plugins/crdt/context';
import { hasOwn, isObject } from '@app/plugins/crdt/functions';
import { getValkeyExtension } from '@app/plugins/crdt/valkey';
import type { CloseEvent } from '@hocuspocus/common';
import { Server } from '@hocuspocus/server';
import { applyUpdateV2 } from 'yjs';

const log = getLogger('collaboration');
const secureLog = getSecureLogger('collaboration');
const teamLog = getTeamLogger('collaboration');

const logContext = (msg: string, context: ConnectionContext, level: Level = 'info') => {
log[level]({
Expand Down Expand Up @@ -225,7 +225,7 @@ export const collaborationServer = Server.configure({
if (!isConnectionContext(context)) {
log.error({ msg: 'Tried to store document without context' });

secureLog.debug({
teamLog.debug({
msg: 'Tried to store document without context',
data: {
document: JSON.stringify(getDocumentJson(document)),
Expand Down
2 changes: 1 addition & 1 deletion server/src/plugins/crdt/context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isObject } from '@app/plugins/crdt/functions';
import { isObject } from '@app/functions/functions';

export interface ConnectionContext {
readonly behandlingId: string;
Expand Down
2 changes: 1 addition & 1 deletion server/src/plugins/crdt/crdt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { getAzureADClient } from '@app/auth/get-auth-client';
import { refreshOnBehalfOfAccessToken } from '@app/auth/on-behalf-of';
import { ApiClientEnum } from '@app/config/config';
import { isDeployed } from '@app/config/env';
import { isObject } from '@app/functions/functions';
import { parseTokenPayload } from '@app/helpers/token-parser';
import { type AnyObject, type Level, type LogArgs, getLogger } from '@app/logger';
import { ACCESS_TOKEN_PLUGIN_ID } from '@app/plugins/access-token';
import { getHeaders } from '@app/plugins/crdt/api/headers';
import { KABAL_API_URL } from '@app/plugins/crdt/api/url';
import { collaborationServer } from '@app/plugins/crdt/collaboration-server';
import type { ConnectionContext } from '@app/plugins/crdt/context';
import { isObject } from '@app/plugins/crdt/functions';
import { NAV_IDENT_PLUGIN_ID } from '@app/plugins/nav-ident';
import { OBO_ACCESS_TOKEN_PLUGIN_ID } from '@app/plugins/obo-token';
import { TAB_ID_PLUGIN_ID } from '@app/plugins/tab-id';
Expand Down