From 44dc3ffd543f22d0837356f09665e160f9d36675 Mon Sep 17 00:00:00 2001 From: Enrico Regge Date: Thu, 17 Jul 2025 22:40:55 +0200 Subject: [PATCH 1/8] Added logging example for each language --- cos-to-sql/package.json | 1 - logging/README.md | 114 +++++++ logging/go/.ceignore | 1 + logging/go/.dockerignore | 4 + logging/go/Dockerfile | 10 + logging/go/build | 15 + logging/go/go.mod | 10 + logging/go/go.sum | 14 + logging/go/main.go | 33 ++ logging/java/.ceignore | 1 + logging/java/.dockerignore | 5 + logging/java/.gitignore | 1 + logging/java/Dockerfile | 16 + logging/java/build | 19 ++ logging/java/pom.xml | 86 +++++ .../com/ibm/cloud/codeengine/sample/App.java | 14 + logging/java/src/main/resources/logback.xml | 17 + logging/node/.ceignore | 1 + logging/node/.dockerignore | 3 + logging/node/Dockerfile | 19 ++ logging/node/index.mjs | 51 +++ logging/node/package-lock.json | 300 ++++++++++++++++++ logging/node/package.json | 16 + logging/python/.dockerignore | 2 + logging/python/Dockerfile | 18 ++ logging/python/main.py | 67 ++++ logging/python/requirements.txt | 0 logging/run | 42 +++ 28 files changed, 879 insertions(+), 1 deletion(-) create mode 100644 logging/README.md create mode 100644 logging/go/.ceignore create mode 100644 logging/go/.dockerignore create mode 100644 logging/go/Dockerfile create mode 100755 logging/go/build create mode 100644 logging/go/go.mod create mode 100644 logging/go/go.sum create mode 100644 logging/go/main.go create mode 100644 logging/java/.ceignore create mode 100644 logging/java/.dockerignore create mode 100644 logging/java/.gitignore create mode 100644 logging/java/Dockerfile create mode 100755 logging/java/build create mode 100644 logging/java/pom.xml create mode 100644 logging/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java create mode 100644 logging/java/src/main/resources/logback.xml create mode 100644 logging/node/.ceignore create mode 100644 logging/node/.dockerignore create mode 100644 logging/node/Dockerfile create mode 100644 logging/node/index.mjs create mode 100644 logging/node/package-lock.json create mode 100644 logging/node/package.json create mode 100644 logging/python/.dockerignore create mode 100644 logging/python/Dockerfile create mode 100644 logging/python/main.py create mode 100644 logging/python/requirements.txt create mode 100755 logging/run diff --git a/cos-to-sql/package.json b/cos-to-sql/package.json index 7b2692a2b..250113ea3 100644 --- a/cos-to-sql/package.json +++ b/cos-to-sql/package.json @@ -5,7 +5,6 @@ "main": "app.mjs", "scripts": { "start": "node .", - "local": "node ./src/job.mjs", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ diff --git a/logging/README.md b/logging/README.md new file mode 100644 index 000000000..50da7fb05 --- /dev/null +++ b/logging/README.md @@ -0,0 +1,114 @@ +# Code Engine logging examples + +The following sample provides recommended example to setup structured logging. + +Use the following command to build and deploy all examples to the Code Engine project of your choice. +``` +ibmcloud ce project select --name + +./run all +``` + +### Node + +* Logging framework: [winston](https://www.npmjs.com/package/winston) +* Sample code: + ``` + logger.debug("This is a simple log message"); + + logger.info("A log entry that adds another key-value pair", { + extra_key: "extra_value", + }); + ``` +* Format of a rendered log lines: + ``` + { "level":"debug", "message":"This is a simple log message", "timestamp":"2025-05-20T10:30:02.407Z" } + { "extra_key": "extra_value", "level":"info", "message":"A log entry that adds another key-value pair", "timestamp":"2025-05-20T10:30:02.407Z" } + ``` +* How to test the example in Code Engine + ``` + ./run node + ``` +* How to test the example, locally + ``` + cd node + npm install + node . + ``` + + +### Python + + +* Logging framework: [built-in logging](https://docs.python.org/3/library/logging.html) +* Sample code: +``` + logger.info("This is a structured log message") + + logger.debug("A structured log entry", extra={"extra_key": "extra_value"}) + +``` +* Format of a rendered log lines: + ``` + { "timestamp": "2025-05-20T10:34:00.318087+00:00", "level": "INFO", "message": "This is a structured log message" } + { "timestamp": "2025-05-20T10:34:00.318087+00:00", "level": "DEBUG", "message": "A structured log entry", "extra_key": "extra_value" } + ``` +* How to test the example in Code Engine + ``` + ./run python + ``` +* How to test the example, locally + ``` + cd python + python3 main.py + ``` + +### Java + + +* Logging framework: [slf4j](https://www.slf4j.org/), [logback](https://logback.qos.ch/) +* Sample code: + ``` + logger.atDebug().addKeyValue("extra_key", "extra_value").log("A log entry that adds another key-value pair"); + ``` +* Format of a rendered log lines: + ``` + { "timestamp": "2025-05-20T10:44:15.501372Z", "message":"A log entry that adds another key-value pair", "thread_name":"main", "level":"DEBUG", "extra_key":"extra_value" } + ``` +* How to test the example in Code Engine + ``` + ./run java + ``` +* How to test the example, locally + ``` + cd java + mvn clean install + java -jar target/logging-1.0.0-SNAPSHOT.jar + ``` + + +### Go + +* Logging framework: [zap](https://github.com/uber-go/zap) +* Sample code: + ``` + logger.Info("This is a simple log message") + logger.Info("A log entry that adds another key-value pair", + zap.String("extra_key", "extra_value"), + ) + ``` +* Format of a rendered log lines: + ``` + {"level":"info","timestamp":"2025-05-20T15:32:12.563+0200","message":"This is a simple log message"} + {"level":"info","timestamp":"2025-05-20T15:32:12.564+0200","message":"A log entry that adds another key-value pair","extra_key":"extra_value"} + ``` +* How to test the example in Code Engine + ``` + ./run go + ``` +* How to test the example, locally + ``` + cd go + CGO_ENABLED=0 go build -o app main.go + ./app + ``` \ No newline at end of file diff --git a/logging/go/.ceignore b/logging/go/.ceignore new file mode 100644 index 000000000..5657f6ea7 --- /dev/null +++ b/logging/go/.ceignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/logging/go/.dockerignore b/logging/go/.dockerignore new file mode 100644 index 000000000..210eadbce --- /dev/null +++ b/logging/go/.dockerignore @@ -0,0 +1,4 @@ +.dockerignore +build +Dockerfile +vendor \ No newline at end of file diff --git a/logging/go/Dockerfile b/logging/go/Dockerfile new file mode 100644 index 000000000..3602d953f --- /dev/null +++ b/logging/go/Dockerfile @@ -0,0 +1,10 @@ +FROM quay.io/projectquay/golang:1.23 AS build-env +WORKDIR /go/src/app +COPY . . + +RUN CGO_ENABLED=0 go build -o /go/bin/app main.go + +# Copy the executable into a smaller base image +FROM gcr.io/distroless/static-debian12 +COPY --from=build-env /go/bin/app / +ENTRYPOINT ["/app"] diff --git a/logging/go/build b/logging/go/build new file mode 100755 index 000000000..cf5e5ce11 --- /dev/null +++ b/logging/go/build @@ -0,0 +1,15 @@ +#!/bin/bash + +# Env Vars: +# REGISTRY: name of the image registry/namespace to store the images +# +# NOTE: to run this you MUST set the REGISTRY environment variable to +# your own image registry/namespace otherwise the `docker push` commands +# will fail due to an auth failure. Which means, you also need to be logged +# into that registry before you run it. + +set -ex +export REGISTRY=${REGISTRY:-icr.io/codeengine} + +# Build and push the image +KO_DOCKER_REPO="${REGISTRY}/logging/go" ko build . --bare --image-user 1001 --platform linux/amd64 --sbom=none diff --git a/logging/go/go.mod b/logging/go/go.mod new file mode 100644 index 000000000..a2e1f827c --- /dev/null +++ b/logging/go/go.mod @@ -0,0 +1,10 @@ +module github.com/IBM/CodeEngine/logging/go + +go 1.23.0 + +require go.uber.org/zap v1.27.0 + +require ( + github.com/stretchr/testify v1.10.0 // indirect + go.uber.org/multierr v1.10.0 // indirect +) diff --git a/logging/go/go.sum b/logging/go/go.sum new file mode 100644 index 000000000..0b9b705a4 --- /dev/null +++ b/logging/go/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logging/go/main.go b/logging/go/main.go new file mode 100644 index 000000000..e50790daf --- /dev/null +++ b/logging/go/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "os" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func init() { + stdout := zapcore.AddSync(os.Stdout) + + level := zap.NewAtomicLevelAt(zap.InfoLevel) + + encoderCfg := zap.NewProductionEncoderConfig() + encoderCfg.TimeKey = "timestamp" + encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder + encoderCfg.MessageKey = "message" + consoleEncoder := zapcore.NewJSONEncoder(encoderCfg) + + zap.ReplaceGlobals(zap.New(zapcore.NewCore(consoleEncoder, stdout, level))) +} + +func main() { + logger := zap.L() + + logger.Info("This is a simple log message") + + logger.Info("A log entry that adds another key-value pair", + zap.String("extra_key", "extra_value"), + ) + +} diff --git a/logging/java/.ceignore b/logging/java/.ceignore new file mode 100644 index 000000000..9f970225a --- /dev/null +++ b/logging/java/.ceignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/logging/java/.dockerignore b/logging/java/.dockerignore new file mode 100644 index 000000000..ba4a1025a --- /dev/null +++ b/logging/java/.dockerignore @@ -0,0 +1,5 @@ +.dockerignore +.gitignore +build +Dockerfile +target diff --git a/logging/java/.gitignore b/logging/java/.gitignore new file mode 100644 index 000000000..1de565933 --- /dev/null +++ b/logging/java/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/logging/java/Dockerfile b/logging/java/Dockerfile new file mode 100644 index 000000000..fb4348e55 --- /dev/null +++ b/logging/java/Dockerfile @@ -0,0 +1,16 @@ +# Download dependencies and compile in builder stage +FROM registry.access.redhat.com/ubi9/openjdk-21 AS builder + +COPY --chown=${UID} . /src +WORKDIR /src +RUN mvn package -Dmaven.test.skip=true + +# Build final stage +FROM gcr.io/distroless/java21 + +COPY --chown=1001:0 --from=builder /src/target/logging-1.0.0-SNAPSHOT.jar /app/logging-1.0.0-SNAPSHOT.jar + +USER 1001:0 +WORKDIR /app + +CMD ["logging-1.0.0-SNAPSHOT.jar"] diff --git a/logging/java/build b/logging/java/build new file mode 100755 index 000000000..ff6dfe9a5 --- /dev/null +++ b/logging/java/build @@ -0,0 +1,19 @@ +#!/bin/bash + +# Env Vars: +# REGISTRY: name of the image registry/namespace to store the images +# NOCACHE: set this to "--no-cache" to turn off the Docker build cache +# +# NOTE: to run this you MUST set the REGISTRY environment variable to +# your own image registry/namespace otherwise the `docker push` commands +# will fail due to an auth failure. Which means, you also need to be logged +# into that registry before you run it. + +set -ex +export REGISTRY=${REGISTRY:-icr.io/codeengine} + +# Build the image +docker build ${NOCACHE} -t ${REGISTRY}/trusted-profiles/java . --platform linux/amd64 + +# And push it +docker push ${REGISTRY}/trusted-profiles/java diff --git a/logging/java/pom.xml b/logging/java/pom.xml new file mode 100644 index 000000000..c4f5355de --- /dev/null +++ b/logging/java/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + com.ibm.cloud.codeengine.sample + logging + 1.0.0-SNAPSHOT + + logging + https://github.com/IBM/CodeEngine + + + UTF-8 + 21 + + + + + org.slf4j + slf4j-api + 2.0.17 + + + ch.qos.logback + logback-classic + 1.5.18 + + + ch.qos.logback + logback-core + 1.5.18 + + + net.logstash.logback + logstash-logback-encoder + 8.1 + + + + + + + + + maven-clean-plugin + 3.4.0 + + + + maven-compiler-plugin + 3.13.0 + + + maven-jar-plugin + 3.4.2 + + + + + + + maven-shade-plugin + 3.6.0 + + false + + + com.ibm.cloud.codeengine.sample.App + + + + + + package + + shade + + + + + + + \ No newline at end of file diff --git a/logging/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java b/logging/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java new file mode 100644 index 000000000..ac89e9678 --- /dev/null +++ b/logging/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java @@ -0,0 +1,14 @@ +package com.ibm.cloud.codeengine.sample; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App { + + public static void main(String[] args) { + + Logger logger = LoggerFactory.getLogger("my_logger"); + logger.info("A simple log message"); + logger.atDebug().addKeyValue("extra_key", "extra_value").log("A structured log entry"); + } +} diff --git a/logging/java/src/main/resources/logback.xml b/logging/java/src/main/resources/logback.xml new file mode 100644 index 000000000..494c92b31 --- /dev/null +++ b/logging/java/src/main/resources/logback.xml @@ -0,0 +1,17 @@ + + + + UTC + + timestamp + [ignore] + [ignore] + [ignore] + + + + + + + + \ No newline at end of file diff --git a/logging/node/.ceignore b/logging/node/.ceignore new file mode 100644 index 000000000..b512c09d4 --- /dev/null +++ b/logging/node/.ceignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/logging/node/.dockerignore b/logging/node/.dockerignore new file mode 100644 index 000000000..0474a63e8 --- /dev/null +++ b/logging/node/.dockerignore @@ -0,0 +1,3 @@ +.dockerignore +Dockerfile +node_modules/ \ No newline at end of file diff --git a/logging/node/Dockerfile b/logging/node/Dockerfile new file mode 100644 index 000000000..2203434a0 --- /dev/null +++ b/logging/node/Dockerfile @@ -0,0 +1,19 @@ +# Download dependencies in builder stage +FROM registry.access.redhat.com/ubi9/nodejs-22:latest AS builder + +COPY --chown=${CNB_USER_ID}:${CNB_GROUP_ID} package.json /app/ +WORKDIR /app +RUN npm i --omit=dev + + +# Use a small distroless image for as runtime image +FROM gcr.io/distroless/nodejs22 + +COPY --chown=1001:0 --from=builder /app/node_modules /app/node_modules +COPY --chown=1001:0 . /app/ + +USER 1001:0 +WORKDIR /app +EXPOSE 8080 + +CMD ["index.mjs"] \ No newline at end of file diff --git a/logging/node/index.mjs b/logging/node/index.mjs new file mode 100644 index 000000000..cb8f06ddf --- /dev/null +++ b/logging/node/index.mjs @@ -0,0 +1,51 @@ +import winston from "winston"; +const { combine, timestamp, json } = winston.format; + +function getCodeEngineLogger(componentName) { + const ceTransport = new winston.transports.Console({ + level: "debug", + format: combine(timestamp(), json()), + }); + + winston.loggers.add(componentName, { + transports: [ceTransport], + }); + + return winston.loggers.get(componentName); +} + +const logger = getCodeEngineLogger("your-logger").child({ correlationId: process.env.CE_JOBRUN }); + +logger.info("This is a structured log message"); +logger.debug("This is a structured log message"); +logger.warn("This is a structured log message"); +logger.error("This is a structured log message"); + +logger.debug("A structured log entry", { + extra_key: "extra_value", +}); + +logger.info("some message that carries a ton of additional fields", { + requestId: crypto.randomUUID(), + userId: "user-123456", + action: "test", + metadata: { + foo: "bar", + timestamp: new Date().toISOString(), + }, +}); + +// Error logging +try { + throw new Error("boom!"); +} catch (err) { + logger.error("Error occurred", err); +} + +// Multi-line log sample +logger.info(`📜 Multi-line log sample: +Line 1: initialization started +Line 2: loading modules +Line 3: modules loaded +Line 4: entering main loop +End of sample`); diff --git a/logging/node/package-lock.json b/logging/node/package-lock.json new file mode 100644 index 000000000..5136f9a9a --- /dev/null +++ b/logging/node/package-lock.json @@ -0,0 +1,300 @@ +{ + "name": "ce-logging", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ce-logging", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "winston": "^3.17.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + } + } +} diff --git a/logging/node/package.json b/logging/node/package.json new file mode 100644 index 000000000..fe5edb73f --- /dev/null +++ b/logging/node/package.json @@ -0,0 +1,16 @@ +{ + "name": "ce-logging", + "version": "1.0.0", + "description": "", + "main": "index.mjs", + "scripts": { + "start": "node .", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "license": "MIT", + "homepage": "https://cloud.ibm.com/containers/serverless", + "dependencies": { + "winston": "^3.17.0" + } + } \ No newline at end of file diff --git a/logging/python/.dockerignore b/logging/python/.dockerignore new file mode 100644 index 000000000..9a715f53b --- /dev/null +++ b/logging/python/.dockerignore @@ -0,0 +1,2 @@ +.dockerignore +Dockerfile \ No newline at end of file diff --git a/logging/python/Dockerfile b/logging/python/Dockerfile new file mode 100644 index 000000000..de687ace9 --- /dev/null +++ b/logging/python/Dockerfile @@ -0,0 +1,18 @@ +# Download dependencies in builder stage +FROM registry.access.redhat.com/ubi9/python-312 AS builder + +COPY requirements.txt . +RUN python -m pip install -r requirements.txt + +# Build final stage +FROM gcr.io/distroless/python3 + +ENV PYTHONPATH=/app/site-packages + +COPY --chown=1001:0 --from=builder /opt/app-root/lib/python3.12/site-packages ${PYTHONPATH} +COPY --chown=1001:0 main.py /app/ + +USER 1001:0 +WORKDIR /app + +CMD ["main.py"] diff --git a/logging/python/main.py b/logging/python/main.py new file mode 100644 index 000000000..5482bf261 --- /dev/null +++ b/logging/python/main.py @@ -0,0 +1,67 @@ +import logging +import json +import uuid +from datetime import datetime, timezone + +# # Create the logger +logger = logging.getLogger("json_logger") +logger.setLevel(logging.DEBUG) + +class JsonFormatter(logging.Formatter): + def format(self, record): + log_entry = { + "timestamp": datetime.now(timezone.utc).isoformat(), # Add UTC timestamp + "level": record.levelname, # Log level (e.g., INFO, DEBUG, ERROR) + "message": record.getMessage() + } + # Add extra fields to the log entry if they exist + if hasattr(record, '__dict__'): + for key, value in record.__dict__.items(): + # Skip standard LogRecord attributes and private attributes + if (key not in ('args', 'asctime', 'created', 'exc_info', 'exc_text', + 'filename', 'funcName', 'id', 'levelname', 'levelno', + 'lineno', 'module', 'msecs', 'message', 'msg', + 'name', 'pathname', 'process', 'processName', + 'relativeCreated', 'stack_info', 'thread', 'threadName', 'taskName') + and not key.startswith('_')): + # Add the extra field to the log entry + log_entry[key] = value + + return json.dumps(log_entry) # Return the log entry as a JSON string + + +# Create a console handler and set the custom JSON formatter +console_handler = logging.StreamHandler() +console_handler.setFormatter(JsonFormatter()) +logger.addHandler(console_handler) + +if __name__ == '__main__': + # Regular messages + logger.info("This is a structured log message") + + # Log a JSON objects (with 'message' key) + logger.debug("A structured log entry", extra={"extra_key": "extra_value"}) + + logger.info("some message that carries a ton of additional fields", extra={ + "requestId": str(uuid.uuid4()), + "userId": "user-123456", + "action": "test", + "metadata": { + "foo": "bar", + "timestamp": datetime.now(timezone.utc).isoformat() + } + }) + + # Logging errors + try: + 1/0 + except Exception as err: + logger.error(f"Error occurred: {err}") + + # Multi-line log sample + logger.info("""📜 Multi-line log sample: +Line 1: initialization started +Line 2: loading modules +Line 3: modules loaded +Line 4: entering main loop +End of sample""") diff --git a/logging/python/requirements.txt b/logging/python/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/logging/run b/logging/run new file mode 100755 index 000000000..b490f6739 --- /dev/null +++ b/logging/run @@ -0,0 +1,42 @@ +#!/bin/bash +set -eo pipefail + +PREFIX="${PREFIX:=logging-sample}" +LANGUAGE="${1:=all}" + +languages=( + node + python + go + java +) + +for i in "${languages[@]}"; do + + if [[ "$LANGUAGE" == "all" || "$LANGUAGE" == "$i" ]];then + echo "Deploying Code Engine job for $i ..." + job_name="$i-${PREFIX}" + + create_or_update=update + if ! ibmcloud ce job get --name $job_name >/dev/null 2>&1; then + echo -e "\nCreating the job '$job_name' ..." + create_or_update=create + else + echo -e "\nUpdating the job '$job_name' ..." + fi + + ibmcloud ce job $create_or_update --n "$job_name" --src . --context-dir "$i/" --memory 0.5G --cpu 0.25 --wait + + echo "Submit a job run for $i ..." + jobrun_name="$job_name-$(openssl rand -hex 6)" + ibmcloud ce jobrun submit --job "$job_name" --name "$jobrun_name" --wait + + echo "Printing the logs for '$jobrun_name' ..." + ibmcloud ce jobrun logs --jobrun "$jobrun_name" + + else + continue; + fi +done + +echo "Done" From c75c3889cb22a58fe7bd7b827e22322086e683a1 Mon Sep 17 00:00:00 2001 From: Enrico Regge Date: Mon, 21 Jul 2025 22:09:25 +0200 Subject: [PATCH 2/8] Added inbound logging to the cos-to-sql app --- cos-to-sql/app.mjs | 113 ++++++++------- cos-to-sql/package-lock.json | 260 ++++++++++++++++++++++++++++++++++- cos-to-sql/package.json | 3 +- cos-to-sql/utils/logging.mjs | 17 +++ 4 files changed, 344 insertions(+), 49 deletions(-) create mode 100644 cos-to-sql/utils/logging.mjs diff --git a/cos-to-sql/app.mjs b/cos-to-sql/app.mjs index ea9605d1f..123569fcf 100644 --- a/cos-to-sql/app.mjs +++ b/cos-to-sql/app.mjs @@ -1,4 +1,5 @@ import { stat } from "fs/promises"; +import crypto from "crypto"; import express from "express"; import favicon from "serve-favicon"; import path from "path"; @@ -15,8 +16,11 @@ import { ContainerAuthenticator } from "ibm-cloud-sdk-core"; import { addUser, closeDBConnection, deleteUsers, getPgClient, listUsers } from "./utils/db.mjs"; import { getObjectContent } from "./utils/cos.mjs"; import { convertCsvToDataStruct } from "./utils/utils.mjs"; +import { getCodeEngineLogger } from "./utils/logging.mjs"; -console.info("Starting the app ..."); +const appLogger = getCodeEngineLogger("app"); + +appLogger.info("Starting the app ..."); const requiredEnvVars = [ "COS_REGION", @@ -28,7 +32,7 @@ const requiredEnvVars = [ requiredEnvVars.forEach((envVarName) => { if (!process.env[envVarName]) { - console.log(`Failed to start app. Missing '${envVarName}' environment variable`); + appLogger.warn(`Failed to start app. Missing '${envVarName}' environment variable`); process.exit(1); } }); @@ -60,18 +64,42 @@ const app = express(); app.use(express.json()); app.use(favicon(path.join(__dirname, "public", "favicon.ico"))); +// helper function to send JSON responses +function sendJSONResponse(request, response, returnCode, jsonObject) { + response.status(returnCode); + response.setHeader("Content-Type", "application/json"); + response.end(JSON.stringify(jsonObject)); + const duration = Date.now() - request.startTime; + request.logger.info(`handled ${request.method} request for '${request.path}' in ${duration}ms; rc: '${returnCode}'`, { + returnCode, + duration_ms: duration, + }); +} + // use router to bundle all routes to / const router = express.Router(); + +// adding a middleware that extracts the correlation id and initializes the logger +router.use((req, res, next) => { + const correlationId = req.header("x-request-id") || crypto.randomBytes(8).toString("hex"); + req.startTime = Date.now(); + req.correlationId = correlationId; + const operationId = `${req.method}:${req.path}`; + req.operationId = operationId; + req.logger = getCodeEngineLogger("handle-request").child({ correlationId, operationId }); + req.logger.info(`handling incoming ${req.method} request for '${req.path}'`); + next(); +}); + app.use("/", router); // // Default http endpoint, which prints the list of all users in the database router.get("/", async (req, res) => { - console.log(`handling / for '${req.url}'`); const pgClient = await getPgClient(secretsManager, smPgSecretId); const allUsers = await listUsers(pgClient); - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ users: allUsers.rows })); + + return sendJSONResponse(req, res, 200, { users: allUsers.rows }); }); // @@ -80,111 +108,102 @@ router.get("/readiness", async (req, res) => { console.log(`handling /readiness`); if (!(await stat("/var/run/secrets/codeengine.cloud.ibm.com/compute-resource-token/token"))) { console.error("Mounting the trusted profile compute resource token is not enabled"); - res.writeHead(500, { "Content-Type": "application/json" }); - res.end('{"error": "Mounting the trusted profile compute resource token is not enabled"}'); - return; + + return sendJSONResponse(req, res, 500, { + error: "Mounting the trusted profile compute resource token is not enabled", + }); } - res.writeHead(200, { "Content-Type": "application/json" }); - res.end('{"status": "ok"}'); - return; + return sendJSONResponse(req, res, 200, { status: "ok" }); }); // // Ingestion endpoint router.post("/cos-to-sql", async (req, res) => { - console.log(`handling /cos-to-sql for '${req.url}'`); - console.log(`request headers: '${JSON.stringify(req.headers)}`); + req.logger.debug(`request headers: '${JSON.stringify(req.headers)}`); const event = req.body; - console.log(`request body: '${event}'`); + req.logger.debug(`request body: '${event}'`); // // assess whether the request payload contains information about the COS file that got updated if (!event) { - console.log("Request does not contain any event data"); - res.writeHead(400, { "Content-Type": "application/json" }); - res.end('{"error": "request does not contain any event data"}'); - return; + req.logger.info("Request does not contain any event data"); + return sendJSONResponse(req, res, 400, { error: "request does not contain any event data" }); } // // make sure that the event relates to a COS write operation if (event.notification.event_type !== "Object:Write") { - console.log(`COS operation '${event.notification.event_type}' does not match expectations 'Object:Write'`); - res.writeHead(400, { "Content-Type": "application/json" }); - res.end(`{"error": "COS operation '${event.notification.event_type}' does not match expectations 'Object:Write'"}`); - return; + req.logger.info(`COS operation '${event.notification.event_type}' does not match expectations 'Object:Write'`); + + return sendJSONResponse(req, res, 400, { + error: `COS operation '${event.notification.event_type}' does not match expectations 'Object:Write'`, + }); } if (event.notification.content_type !== "text/csv") { - console.log( + req.logger.info( `COS update did happen on file '${event.key}' which is of type '${event.notification.content_type}' (expected type 'text/csv')` ); - res.writeHead(400, { "Content-Type": "application/json" }); - res.end( - `{"error": "COS update did happen on file '${event.key}' which is of type '${event.notification.content_type}' (expected type 'text/csv')"}` - ); - return; + + return sendJSONResponse(req, res, 400, { + error: `COS update did happen on file '${event.key}' which is of type '${event.notification.content_type}' (expected type 'text/csv')`, + }); } - console.log(`Received a COS update event on the CSV file '${event.key}' in bucket '${event.bucket}'`); + req.logger.info(`Received a COS update event on the CSV file '${event.key}' in bucket '${event.bucket}'`); // // Retrieve the COS object that got updated - console.log(`Retrieving file content of '${event.key}' from bucket ${event.bucket} ...`); + req.logger.info(`Retrieving file content of '${event.key}' from bucket ${event.bucket} ...`); const fileContent = await getObjectContent(cosAuthenticator, cosRegion, event.bucket, event.key); // // Convert CSV to a object structure representing an array of users - console.log(`Converting CSV data to a data struct ...`); + req.logger.info(`Converting CSV data to a data struct ...`); const users = await convertCsvToDataStruct(fileContent); - console.log(`users: ${JSON.stringify(users)}`); + req.logger.info(`users: ${JSON.stringify(users)}`); const pgClient = await getPgClient(secretsManager, smPgSecretId); // // Iterate through the list of users - console.log(`Writing converted CSV data to the PostgreSQL database ...`); + req.logger.info(`Writing converted CSV data to the PostgreSQL database ...`); let numberOfProcessedUsers = 0; for (const userToAdd of users) { try { // Perform a single SQL insert statement per user const result = await addUser(pgClient, userToAdd.Firstname, userToAdd.Lastname); - console.log(`Added ${JSON.stringify(userToAdd)} -> ${JSON.stringify(result)}`); + req.logger.info(`Added ${JSON.stringify(userToAdd)} -> ${JSON.stringify(result)}`); numberOfProcessedUsers++; } catch (err) { - console.error(`Failed to add user '${JSON.stringify(userToAdd)}' to the database`, err); + req.logger.error(`Failed to add user '${JSON.stringify(userToAdd)}' to the database`, err); } } - console.log(`Processed ${numberOfProcessedUsers} user records!`); - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(`{"status": "done"}`); - return; + req.logger.info(`Processed ${numberOfProcessedUsers} user records!`); + return sendJSONResponse(req, res, 200, { status: "done" }); }); // // Endpoint that drops the users table router.get("/clear", async (req, res) => { - console.log(`handling /clear for '${req.url}'`); const pgClient = await getPgClient(secretsManager, smPgSecretId); await deleteUsers(pgClient); - console.log(`Deletions done!`); - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(`{"status": "done"}`); - return; + req.logger.info(`Deletions done!`); + return sendJSONResponse(req, res, 200, { status: "done" }); }); // start server const port = 8080; const server = app.listen(port, () => { - console.log(`Server is up and running on port ${port}!`); + appLogger.info(`Server is up and running on port ${port}!`); }); process.on("SIGTERM", async () => { - console.info("SIGTERM signal received."); + appLogger.info("SIGTERM signal received."); await closeDBConnection(); server.close(() => { - console.log("Http server closed."); + appLogger.info("Http server closed."); }); }); diff --git a/cos-to-sql/package-lock.json b/cos-to-sql/package-lock.json index fbb34d22a..f2777f4de 100644 --- a/cos-to-sql/package-lock.json +++ b/cos-to-sql/package-lock.json @@ -15,7 +15,28 @@ "ibm-cloud-sdk-core": "^5.3.0", "pg": "^8.14.0", "pg-connection-string": "^2.7.0", - "serve-favicon": "^2.5.0" + "serve-favicon": "^2.5.0", + "winston": "^3.17.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" } }, "node_modules/@ibm-cloud/secrets-manager": { @@ -61,6 +82,12 @@ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -89,6 +116,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -210,6 +243,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -342,6 +420,12 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -469,6 +553,12 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/file-type": { "version": "16.5.4", "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", @@ -515,6 +605,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -722,6 +818,24 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -767,6 +881,12 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -802,6 +922,23 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -899,6 +1036,15 @@ "node": ">= 0.8" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1213,6 +1359,15 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1385,6 +1540,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -1393,6 +1557,15 @@ "node": ">= 10.x" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1425,6 +1598,12 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1463,6 +1642,15 @@ "node": ">=6" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1505,6 +1693,12 @@ "requires-port": "^1.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1521,6 +1715,70 @@ "node": ">= 0.8" } }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/cos-to-sql/package.json b/cos-to-sql/package.json index 250113ea3..cf9686c73 100644 --- a/cos-to-sql/package.json +++ b/cos-to-sql/package.json @@ -22,6 +22,7 @@ "ibm-cloud-sdk-core": "^5.3.0", "pg": "^8.14.0", "pg-connection-string": "^2.7.0", - "serve-favicon": "^2.5.0" + "serve-favicon": "^2.5.0", + "winston": "^3.17.0" } } diff --git a/cos-to-sql/utils/logging.mjs b/cos-to-sql/utils/logging.mjs new file mode 100644 index 000000000..ce450dd46 --- /dev/null +++ b/cos-to-sql/utils/logging.mjs @@ -0,0 +1,17 @@ +import winston from "winston"; +const { combine, timestamp, json } = winston.format; + +export function getCodeEngineLogger(componentName) { + if (!winston.loggers.get(componentName)) { + const ceTransport = new winston.transports.Console({ + level: "debug", + format: combine(timestamp(), json()), + defaultMeta: { logger: componentName }, + }); + + winston.loggers.add(componentName, { + transports: [ceTransport], + }); + } + return winston.loggers.get(componentName); +} From c9c6e6fc46a031879093143c1032eb028dea1037 Mon Sep 17 00:00:00 2001 From: groezing_pub <80272930+groezing@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:05:47 +0100 Subject: [PATCH 3/8] add unstructured log messages to example --- logging/node/index.mjs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/logging/node/index.mjs b/logging/node/index.mjs index cb8f06ddf..581ee187a 100644 --- a/logging/node/index.mjs +++ b/logging/node/index.mjs @@ -16,10 +16,16 @@ function getCodeEngineLogger(componentName) { const logger = getCodeEngineLogger("your-logger").child({ correlationId: process.env.CE_JOBRUN }); -logger.info("This is a structured log message"); -logger.debug("This is a structured log message"); -logger.warn("This is a structured log message"); -logger.error("This is a structured log message"); +//write example unstructured log +process.stdout.write('1.This is a unstructured log message without a severity identifier\n'); +process.stdout.write('2.This is a unstructured log message with a severity identifier INFO \n'); +process.stdout.write('3.This is a unstructured log message with a severity identifier ERROR \n'); + + +logger.info("4.This is a structured log message"); +logger.debug("5.This is a structured log message"); +logger.warn("6.This is a structured log message"); +logger.error("7.This is a structured log message"); logger.debug("A structured log entry", { extra_key: "extra_value", From f1366160f43cd4105deeb74d381a13a7a1210f54 Mon Sep 17 00:00:00 2001 From: groezing_pub <80272930+groezing@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:30:43 +0100 Subject: [PATCH 4/8] Update index.mjs --- logging/node/index.mjs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/logging/node/index.mjs b/logging/node/index.mjs index 581ee187a..b7a9c583f 100644 --- a/logging/node/index.mjs +++ b/logging/node/index.mjs @@ -20,12 +20,13 @@ const logger = getCodeEngineLogger("your-logger").child({ correlationId: process process.stdout.write('1.This is a unstructured log message without a severity identifier\n'); process.stdout.write('2.This is a unstructured log message with a severity identifier INFO \n'); process.stdout.write('3.This is a unstructured log message with a severity identifier ERROR \n'); +process.stderr.write('4.This is a unstructured log message with a severity identifier ERROR \n'); -logger.info("4.This is a structured log message"); -logger.debug("5.This is a structured log message"); -logger.warn("6.This is a structured log message"); -logger.error("7.This is a structured log message"); +logger.info("5.This is a structured log message"); +logger.debug("6.This is a structured log message"); +logger.warn("7.This is a structured log message"); +logger.error("8.This is a structured log message"); logger.debug("A structured log entry", { extra_key: "extra_value", From 09af35680011a405730cc01132a4df43f0d94688 Mon Sep 17 00:00:00 2001 From: Enrico Regge Date: Thu, 26 Feb 2026 13:49:41 +0100 Subject: [PATCH 5/8] reworked node examples --- logging/{node => node-structured}/.ceignore | 0 .../{node => node-structured}/.dockerignore | 0 logging/{node => node-structured}/Dockerfile | 4 +- logging/node-structured/index.mjs | 42 ++++++++++++++ .../package-lock.json | 0 .../{node => node-structured}/package.json | 0 logging/node-unstructured/.ceignore | 1 + logging/node-unstructured/.dockerignore | 3 + logging/node-unstructured/Dockerfile | 18 ++++++ logging/node-unstructured/index.mjs | 18 ++++++ logging/node-unstructured/package.json | 15 +++++ logging/node/index.mjs | 58 ------------------- logging/run | 3 +- 13 files changed, 101 insertions(+), 61 deletions(-) rename logging/{node => node-structured}/.ceignore (100%) rename logging/{node => node-structured}/.dockerignore (100%) rename logging/{node => node-structured}/Dockerfile (78%) create mode 100644 logging/node-structured/index.mjs rename logging/{node => node-structured}/package-lock.json (100%) rename logging/{node => node-structured}/package.json (100%) create mode 100644 logging/node-unstructured/.ceignore create mode 100644 logging/node-unstructured/.dockerignore create mode 100644 logging/node-unstructured/Dockerfile create mode 100644 logging/node-unstructured/index.mjs create mode 100644 logging/node-unstructured/package.json delete mode 100644 logging/node/index.mjs diff --git a/logging/node/.ceignore b/logging/node-structured/.ceignore similarity index 100% rename from logging/node/.ceignore rename to logging/node-structured/.ceignore diff --git a/logging/node/.dockerignore b/logging/node-structured/.dockerignore similarity index 100% rename from logging/node/.dockerignore rename to logging/node-structured/.dockerignore diff --git a/logging/node/Dockerfile b/logging/node-structured/Dockerfile similarity index 78% rename from logging/node/Dockerfile rename to logging/node-structured/Dockerfile index 2203434a0..f3f043303 100644 --- a/logging/node/Dockerfile +++ b/logging/node-structured/Dockerfile @@ -1,5 +1,5 @@ # Download dependencies in builder stage -FROM registry.access.redhat.com/ubi9/nodejs-22:latest AS builder +FROM registry.access.redhat.com/ubi9/nodejs-24:latest AS builder COPY --chown=${CNB_USER_ID}:${CNB_GROUP_ID} package.json /app/ WORKDIR /app @@ -7,7 +7,7 @@ RUN npm i --omit=dev # Use a small distroless image for as runtime image -FROM gcr.io/distroless/nodejs22 +FROM gcr.io/distroless/nodejs24 COPY --chown=1001:0 --from=builder /app/node_modules /app/node_modules COPY --chown=1001:0 . /app/ diff --git a/logging/node-structured/index.mjs b/logging/node-structured/index.mjs new file mode 100644 index 000000000..b39a9bd40 --- /dev/null +++ b/logging/node-structured/index.mjs @@ -0,0 +1,42 @@ +import winston from "winston"; +const { combine, json } = winston.format; + +const logger = winston.createLogger({ + level: "debug", + transports: [new winston.transports.Console()], + format: combine(json()), +}); + +logger.info("1.This is a structured log message"); +logger.debug("2.This is a structured log message"); +logger.warn("3.This is a structured log message"); +logger.error("4.This is a structured log message"); + +logger.debug("5. A structured log entry", { + extra_key: "extra_value", +}); + +logger.info("6. some message that carries a ton of additional fields", { + requestId: crypto.randomUUID(), + userId: "user-123456", + action: "test", + metadata: { + foo: "bar", + timestamp: new Date().toISOString(), + }, +}); + +// Error logging +try { + throw new Error("boom!"); +} catch (err) { + logger.error("7. Error occurred", err); +} + +// Multi-line log sample +logger.info(`8. Multi-line log sample: +Line 1: initialization started +Line 2: loading modules +Line 3: modules loaded +Line 4: entering main loop +End of sample`); diff --git a/logging/node/package-lock.json b/logging/node-structured/package-lock.json similarity index 100% rename from logging/node/package-lock.json rename to logging/node-structured/package-lock.json diff --git a/logging/node/package.json b/logging/node-structured/package.json similarity index 100% rename from logging/node/package.json rename to logging/node-structured/package.json diff --git a/logging/node-unstructured/.ceignore b/logging/node-unstructured/.ceignore new file mode 100644 index 000000000..b512c09d4 --- /dev/null +++ b/logging/node-unstructured/.ceignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/logging/node-unstructured/.dockerignore b/logging/node-unstructured/.dockerignore new file mode 100644 index 000000000..0474a63e8 --- /dev/null +++ b/logging/node-unstructured/.dockerignore @@ -0,0 +1,3 @@ +.dockerignore +Dockerfile +node_modules/ \ No newline at end of file diff --git a/logging/node-unstructured/Dockerfile b/logging/node-unstructured/Dockerfile new file mode 100644 index 000000000..d1f9ae7f8 --- /dev/null +++ b/logging/node-unstructured/Dockerfile @@ -0,0 +1,18 @@ +# Download dependencies in builder stage +FROM registry.access.redhat.com/ubi9/nodejs-24:latest AS builder + +COPY --chown=${CNB_USER_ID}:${CNB_GROUP_ID} package.json /app/ +WORKDIR /app +RUN npm i --omit=dev + + +# Use a small distroless image for as runtime image +FROM gcr.io/distroless/nodejs24 + +COPY --chown=1001:0 . /app/ + +USER 1001:0 +WORKDIR /app +EXPOSE 8080 + +CMD ["index.mjs"] \ No newline at end of file diff --git a/logging/node-unstructured/index.mjs b/logging/node-unstructured/index.mjs new file mode 100644 index 000000000..7cde93860 --- /dev/null +++ b/logging/node-unstructured/index.mjs @@ -0,0 +1,18 @@ + +// Write example unstructured logs +console.log("This is a unstructured log message without a severity identifier"); +console.log("This is a unstructured log message with a severity identifier WARN \n"); +console.warn("This is a unstructured log message using a specific level"); +console.log("ERROR This is a unstructured log message prefixed with the level"); +console.log(`${new Date().toISOString()} This is a unstructured log message prefixed with the timestamp`); +console.log(`${new Date().toISOString()} DEBUG This is a unstructured log message prefixed with the timestamp and level`); + +// Multi-line example +console.log("Multi-line log sample...\\nStep 1: Validating input...\\nStep 2: Processing payment..."); + +// Error logging +try { + throw new Error("boom!"); +} catch (err) { + console.error("Stacktrace example", err); +} diff --git a/logging/node-unstructured/package.json b/logging/node-unstructured/package.json new file mode 100644 index 000000000..3b4f247d4 --- /dev/null +++ b/logging/node-unstructured/package.json @@ -0,0 +1,15 @@ +{ + "name": "ce-logging", + "version": "1.0.0", + "description": "", + "main": "index.mjs", + "scripts": { + "start": "node .", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "license": "MIT", + "homepage": "https://cloud.ibm.com/containers/serverless", + "dependencies": { + } + } \ No newline at end of file diff --git a/logging/node/index.mjs b/logging/node/index.mjs deleted file mode 100644 index b7a9c583f..000000000 --- a/logging/node/index.mjs +++ /dev/null @@ -1,58 +0,0 @@ -import winston from "winston"; -const { combine, timestamp, json } = winston.format; - -function getCodeEngineLogger(componentName) { - const ceTransport = new winston.transports.Console({ - level: "debug", - format: combine(timestamp(), json()), - }); - - winston.loggers.add(componentName, { - transports: [ceTransport], - }); - - return winston.loggers.get(componentName); -} - -const logger = getCodeEngineLogger("your-logger").child({ correlationId: process.env.CE_JOBRUN }); - -//write example unstructured log -process.stdout.write('1.This is a unstructured log message without a severity identifier\n'); -process.stdout.write('2.This is a unstructured log message with a severity identifier INFO \n'); -process.stdout.write('3.This is a unstructured log message with a severity identifier ERROR \n'); -process.stderr.write('4.This is a unstructured log message with a severity identifier ERROR \n'); - - -logger.info("5.This is a structured log message"); -logger.debug("6.This is a structured log message"); -logger.warn("7.This is a structured log message"); -logger.error("8.This is a structured log message"); - -logger.debug("A structured log entry", { - extra_key: "extra_value", -}); - -logger.info("some message that carries a ton of additional fields", { - requestId: crypto.randomUUID(), - userId: "user-123456", - action: "test", - metadata: { - foo: "bar", - timestamp: new Date().toISOString(), - }, -}); - -// Error logging -try { - throw new Error("boom!"); -} catch (err) { - logger.error("Error occurred", err); -} - -// Multi-line log sample -logger.info(`📜 Multi-line log sample: -Line 1: initialization started -Line 2: loading modules -Line 3: modules loaded -Line 4: entering main loop -End of sample`); diff --git a/logging/run b/logging/run index b490f6739..fef700f9c 100755 --- a/logging/run +++ b/logging/run @@ -5,7 +5,8 @@ PREFIX="${PREFIX:=logging-sample}" LANGUAGE="${1:=all}" languages=( - node + node-unstructured + node-structured python go java From de6b3ae49a0bedb5017d7f45d55383ae3f1e4e7c Mon Sep 17 00:00:00 2001 From: Enrico Regge Date: Mon, 2 Mar 2026 00:06:25 +0100 Subject: [PATCH 6/8] finalized examples --- logging/.ceignore | 3 + logging/README.md | 110 +------ logging/{go => go-structured}/.ceignore | 0 logging/{go => go-structured}/.dockerignore | 0 logging/{go => go-structured}/Dockerfile | 2 +- logging/go-structured/go.mod | 3 + .../requirements.txt => go-structured/go.sum} | 0 logging/go-structured/main.go | 67 ++++ logging/go-unstructured/.ceignore | 1 + logging/go-unstructured/.dockerignore | 4 + logging/go-unstructured/Dockerfile | 10 + logging/go-unstructured/go.mod | 3 + logging/go-unstructured/go.sum | 0 logging/go-unstructured/main.go | 43 +++ logging/go/build | 15 - logging/go/go.mod | 10 - logging/go/go.sum | 14 - logging/go/main.go | 33 -- logging/{java => java-structured}/.ceignore | 0 .../{java => java-structured}/.dockerignore | 0 logging/{java => java-structured}/.gitignore | 0 logging/{java => java-structured}/Dockerfile | 0 logging/{java => java-structured}/pom.xml | 0 .../com/ibm/cloud/codeengine/sample/App.java | 56 ++++ .../src/main/resources/logback.xml | 3 +- logging/java-unstructured/.ceignore | 1 + logging/java-unstructured/.dockerignore | 5 + logging/java-unstructured/.gitignore | 1 + logging/java-unstructured/Dockerfile | 16 + logging/java-unstructured/pom.xml | 66 ++++ .../com/ibm/cloud/codeengine/sample/App.java | 46 +++ logging/java/build | 19 -- .../com/ibm/cloud/codeengine/sample/App.java | 14 - logging/node-structured/index.mjs | 45 ++- logging/node-structured/package-lock.json | 300 ------------------ logging/node-structured/package.json | 2 +- logging/node-unstructured/index.mjs | 27 +- .../.dockerignore | 0 .../{python => python-structured}/Dockerfile | 0 logging/python-structured/main.py | 87 +++++ logging/python-structured/requirements.txt | 1 + logging/python-unstructured/.dockerignore | 2 + logging/python-unstructured/Dockerfile | 18 ++ logging/python-unstructured/main.py | 31 ++ logging/python-unstructured/requirements.txt | 0 logging/python/main.py | 67 ---- logging/run | 11 +- 47 files changed, 529 insertions(+), 607 deletions(-) create mode 100644 logging/.ceignore rename logging/{go => go-structured}/.ceignore (100%) rename logging/{go => go-structured}/.dockerignore (100%) rename logging/{go => go-structured}/Dockerfile (81%) create mode 100644 logging/go-structured/go.mod rename logging/{python/requirements.txt => go-structured/go.sum} (100%) create mode 100644 logging/go-structured/main.go create mode 100644 logging/go-unstructured/.ceignore create mode 100644 logging/go-unstructured/.dockerignore create mode 100644 logging/go-unstructured/Dockerfile create mode 100644 logging/go-unstructured/go.mod create mode 100644 logging/go-unstructured/go.sum create mode 100644 logging/go-unstructured/main.go delete mode 100755 logging/go/build delete mode 100644 logging/go/go.mod delete mode 100644 logging/go/go.sum delete mode 100644 logging/go/main.go rename logging/{java => java-structured}/.ceignore (100%) rename logging/{java => java-structured}/.dockerignore (100%) rename logging/{java => java-structured}/.gitignore (100%) rename logging/{java => java-structured}/Dockerfile (100%) rename logging/{java => java-structured}/pom.xml (100%) create mode 100644 logging/java-structured/src/main/java/com/ibm/cloud/codeengine/sample/App.java rename logging/{java => java-structured}/src/main/resources/logback.xml (84%) create mode 100644 logging/java-unstructured/.ceignore create mode 100644 logging/java-unstructured/.dockerignore create mode 100644 logging/java-unstructured/.gitignore create mode 100644 logging/java-unstructured/Dockerfile create mode 100644 logging/java-unstructured/pom.xml create mode 100644 logging/java-unstructured/src/main/java/com/ibm/cloud/codeengine/sample/App.java delete mode 100755 logging/java/build delete mode 100644 logging/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java delete mode 100644 logging/node-structured/package-lock.json rename logging/{python => python-structured}/.dockerignore (100%) rename logging/{python => python-structured}/Dockerfile (100%) create mode 100644 logging/python-structured/main.py create mode 100644 logging/python-structured/requirements.txt create mode 100644 logging/python-unstructured/.dockerignore create mode 100644 logging/python-unstructured/Dockerfile create mode 100644 logging/python-unstructured/main.py create mode 100644 logging/python-unstructured/requirements.txt delete mode 100644 logging/python/main.py diff --git a/logging/.ceignore b/logging/.ceignore new file mode 100644 index 000000000..ef43a4df7 --- /dev/null +++ b/logging/.ceignore @@ -0,0 +1,3 @@ +node_modules/ +target/ +vendor/ \ No newline at end of file diff --git a/logging/README.md b/logging/README.md index 50da7fb05..7c994b868 100644 --- a/logging/README.md +++ b/logging/README.md @@ -1,6 +1,6 @@ # Code Engine logging examples -The following sample provides recommended example to setup structured logging. +The following sample provides recommended example to demonstrate **unstructured** and **structured** logging. Use the following command to build and deploy all examples to the Code Engine project of your choice. ``` @@ -9,106 +9,8 @@ ibmcloud ce project select --name ./run all ``` -### Node - -* Logging framework: [winston](https://www.npmjs.com/package/winston) -* Sample code: - ``` - logger.debug("This is a simple log message"); - - logger.info("A log entry that adds another key-value pair", { - extra_key: "extra_value", - }); - ``` -* Format of a rendered log lines: - ``` - { "level":"debug", "message":"This is a simple log message", "timestamp":"2025-05-20T10:30:02.407Z" } - { "extra_key": "extra_value", "level":"info", "message":"A log entry that adds another key-value pair", "timestamp":"2025-05-20T10:30:02.407Z" } - ``` -* How to test the example in Code Engine - ``` - ./run node - ``` -* How to test the example, locally - ``` - cd node - npm install - node . - ``` - - -### Python - - -* Logging framework: [built-in logging](https://docs.python.org/3/library/logging.html) -* Sample code: -``` - logger.info("This is a structured log message") - - logger.debug("A structured log entry", extra={"extra_key": "extra_value"}) - -``` -* Format of a rendered log lines: - ``` - { "timestamp": "2025-05-20T10:34:00.318087+00:00", "level": "INFO", "message": "This is a structured log message" } - { "timestamp": "2025-05-20T10:34:00.318087+00:00", "level": "DEBUG", "message": "A structured log entry", "extra_key": "extra_value" } - ``` -* How to test the example in Code Engine - ``` - ./run python - ``` -* How to test the example, locally - ``` - cd python - python3 main.py - ``` - -### Java - - -* Logging framework: [slf4j](https://www.slf4j.org/), [logback](https://logback.qos.ch/) -* Sample code: - ``` - logger.atDebug().addKeyValue("extra_key", "extra_value").log("A log entry that adds another key-value pair"); - ``` -* Format of a rendered log lines: - ``` - { "timestamp": "2025-05-20T10:44:15.501372Z", "message":"A log entry that adds another key-value pair", "thread_name":"main", "level":"DEBUG", "extra_key":"extra_value" } - ``` -* How to test the example in Code Engine - ``` - ./run java - ``` -* How to test the example, locally - ``` - cd java - mvn clean install - java -jar target/logging-1.0.0-SNAPSHOT.jar - ``` - - -### Go - -* Logging framework: [zap](https://github.com/uber-go/zap) -* Sample code: - ``` - logger.Info("This is a simple log message") - logger.Info("A log entry that adds another key-value pair", - zap.String("extra_key", "extra_value"), - ) - ``` -* Format of a rendered log lines: - ``` - {"level":"info","timestamp":"2025-05-20T15:32:12.563+0200","message":"This is a simple log message"} - {"level":"info","timestamp":"2025-05-20T15:32:12.564+0200","message":"A log entry that adds another key-value pair","extra_key":"extra_value"} - ``` -* How to test the example in Code Engine - ``` - ./run go - ``` -* How to test the example, locally - ``` - cd go - CGO_ENABLED=0 go build -o app main.go - ./app - ``` \ No newline at end of file +For structured logs, following libraries have been used: +* Node.js: [winston](https://www.npmjs.com/package/winston) +* Python: [Loguru](https://github.com/Delgan/loguru) +* Java: [SLF4J](https://www.slf4j.org/), [Logback](https://logback.qos.ch/) +* Golang: [built-in slog](https://go.dev/blog/slog) diff --git a/logging/go/.ceignore b/logging/go-structured/.ceignore similarity index 100% rename from logging/go/.ceignore rename to logging/go-structured/.ceignore diff --git a/logging/go/.dockerignore b/logging/go-structured/.dockerignore similarity index 100% rename from logging/go/.dockerignore rename to logging/go-structured/.dockerignore diff --git a/logging/go/Dockerfile b/logging/go-structured/Dockerfile similarity index 81% rename from logging/go/Dockerfile rename to logging/go-structured/Dockerfile index 3602d953f..98dff608a 100644 --- a/logging/go/Dockerfile +++ b/logging/go-structured/Dockerfile @@ -1,4 +1,4 @@ -FROM quay.io/projectquay/golang:1.23 AS build-env +FROM quay.io/projectquay/golang:1.25 AS build-env WORKDIR /go/src/app COPY . . diff --git a/logging/go-structured/go.mod b/logging/go-structured/go.mod new file mode 100644 index 000000000..4eaf5ac81 --- /dev/null +++ b/logging/go-structured/go.mod @@ -0,0 +1,3 @@ +module github.com/IBM/CodeEngine/logging/go + +go 1.25.0 diff --git a/logging/python/requirements.txt b/logging/go-structured/go.sum similarity index 100% rename from logging/python/requirements.txt rename to logging/go-structured/go.sum diff --git a/logging/go-structured/main.go b/logging/go-structured/main.go new file mode 100644 index 000000000..3fa30de50 --- /dev/null +++ b/logging/go-structured/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "errors" + "log/slog" + "os" + "runtime/debug" +) + +func main() { + handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + // Remove time and rename msg->message + ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr { + // Drop the time attribute + if attr.Key == slog.TimeKey { + return slog.Attr{} // empty => removed + } + // Rename msg to message + if attr.Key == slog.MessageKey { + return slog.String("message", attr.Value.String()) + } + return attr + }, + }) + logger := slog.New(handler) + + // Expect to be rendered as INFO level log message + logger.Info("This is a structured log message") + + // Expect to be rendered as DEBUG level log message + logger.Debug("This is a structured log message") + + // Expect to be rendered as WARN level log message + logger.Warn("This is a structured log message") + + // Expect to be rendered as ERROR level log message + logger.Error("This is a structured log message") + + // Expect to be rendered as DEBUG level log message. The extra key is available as a searchable, filterable field + logger.Debug("A structured log entry that contains an extra key", + slog.String("extra_key", "extra_value"), + ) + + // Expect to be rendered as INFO level log message. The additional JSON struct is available as a searchable, filterable fields + logger.Info("A structured log entry that carries a ton of additional fields", + slog.String("requestId", "some-request-id"), + slog.String("userId", "user-123456"), + slog.String("action", "test"), + slog.Group("metadata", + slog.String("foo", "bar"), + ), + ) + + // Multi-line example. Expect to be rendered in a single log message + logger.Info(`Multi-line log sample: + Line 1: initialization started + Line 2: loading modules + Line 3: modules loaded + Line 4: entering main loop\nEnd of sample`) + + // Error logging. The error stack trace is rendered in a single log message (see field stack) + err := errors.New("boom!") + logger.Error("An error occurred", + slog.Any("err", err), + slog.String("stack", string(debug.Stack())), + ) +} diff --git a/logging/go-unstructured/.ceignore b/logging/go-unstructured/.ceignore new file mode 100644 index 000000000..5657f6ea7 --- /dev/null +++ b/logging/go-unstructured/.ceignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/logging/go-unstructured/.dockerignore b/logging/go-unstructured/.dockerignore new file mode 100644 index 000000000..210eadbce --- /dev/null +++ b/logging/go-unstructured/.dockerignore @@ -0,0 +1,4 @@ +.dockerignore +build +Dockerfile +vendor \ No newline at end of file diff --git a/logging/go-unstructured/Dockerfile b/logging/go-unstructured/Dockerfile new file mode 100644 index 000000000..98dff608a --- /dev/null +++ b/logging/go-unstructured/Dockerfile @@ -0,0 +1,10 @@ +FROM quay.io/projectquay/golang:1.25 AS build-env +WORKDIR /go/src/app +COPY . . + +RUN CGO_ENABLED=0 go build -o /go/bin/app main.go + +# Copy the executable into a smaller base image +FROM gcr.io/distroless/static-debian12 +COPY --from=build-env /go/bin/app / +ENTRYPOINT ["/app"] diff --git a/logging/go-unstructured/go.mod b/logging/go-unstructured/go.mod new file mode 100644 index 000000000..4eaf5ac81 --- /dev/null +++ b/logging/go-unstructured/go.mod @@ -0,0 +1,3 @@ +module github.com/IBM/CodeEngine/logging/go + +go 1.25.0 diff --git a/logging/go-unstructured/go.sum b/logging/go-unstructured/go.sum new file mode 100644 index 000000000..e69de29bb diff --git a/logging/go-unstructured/main.go b/logging/go-unstructured/main.go new file mode 100644 index 000000000..44ee7a4b1 --- /dev/null +++ b/logging/go-unstructured/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "runtime/debug" + "time" +) + +// This simple Code Engine job demonstrates how to write unstructured log lines +// using the built-in capabilities of Golang + +func main() { + + // expect to be rendered as INFO level log message + fmt.Println("This is a unstructured log message without a severity identifier") + + // expect to be rendered as WARN level log message + fmt.Println("This is a unstructured log message with a severity identifier WARN") + + // expect to be rendered as ERROR level log message, without the keyword ERROR being part of the message + fmt.Println("ERROR This is a unstructured log message prefixed with the level") + + // expect to be rendered as INFO level log message, without the timestamp being part of the message + fmt.Println(time.Now().UTC().Format("2006-01-02T15:04:05.000Z") + " This is a unstructured log message prefixed with the timestamp") + + // expect to be rendered as DEBUG level log message, without the timestamp and keyword DEBUG being part of the message + fmt.Println(time.Now().UTC().Format("2006-01-02T15:04:05.000Z") + " DEBUG This is a unstructured log message prefixed with the timestamp and level") + + // Multi-line example. Expect to be rendered in a single log message + fmt.Println("Multi-line log sample...\\nStep 1: Validating input...\\nStep 2: Processing payment...") + + // Error logging. Expect that the stacktrace is rendered in multiple log statements + // Note: Use structure logs to support multi-line error stack traces + func() { + defer func() { + if r := recover(); r != nil { + fmt.Println("Stacktrace example", r) + fmt.Print(string(debug.Stack())) + } + }() + panic("boom!") + }() +} diff --git a/logging/go/build b/logging/go/build deleted file mode 100755 index cf5e5ce11..000000000 --- a/logging/go/build +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# Env Vars: -# REGISTRY: name of the image registry/namespace to store the images -# -# NOTE: to run this you MUST set the REGISTRY environment variable to -# your own image registry/namespace otherwise the `docker push` commands -# will fail due to an auth failure. Which means, you also need to be logged -# into that registry before you run it. - -set -ex -export REGISTRY=${REGISTRY:-icr.io/codeengine} - -# Build and push the image -KO_DOCKER_REPO="${REGISTRY}/logging/go" ko build . --bare --image-user 1001 --platform linux/amd64 --sbom=none diff --git a/logging/go/go.mod b/logging/go/go.mod deleted file mode 100644 index a2e1f827c..000000000 --- a/logging/go/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module github.com/IBM/CodeEngine/logging/go - -go 1.23.0 - -require go.uber.org/zap v1.27.0 - -require ( - github.com/stretchr/testify v1.10.0 // indirect - go.uber.org/multierr v1.10.0 // indirect -) diff --git a/logging/go/go.sum b/logging/go/go.sum deleted file mode 100644 index 0b9b705a4..000000000 --- a/logging/go/go.sum +++ /dev/null @@ -1,14 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logging/go/main.go b/logging/go/main.go deleted file mode 100644 index e50790daf..000000000 --- a/logging/go/main.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "os" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -func init() { - stdout := zapcore.AddSync(os.Stdout) - - level := zap.NewAtomicLevelAt(zap.InfoLevel) - - encoderCfg := zap.NewProductionEncoderConfig() - encoderCfg.TimeKey = "timestamp" - encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder - encoderCfg.MessageKey = "message" - consoleEncoder := zapcore.NewJSONEncoder(encoderCfg) - - zap.ReplaceGlobals(zap.New(zapcore.NewCore(consoleEncoder, stdout, level))) -} - -func main() { - logger := zap.L() - - logger.Info("This is a simple log message") - - logger.Info("A log entry that adds another key-value pair", - zap.String("extra_key", "extra_value"), - ) - -} diff --git a/logging/java/.ceignore b/logging/java-structured/.ceignore similarity index 100% rename from logging/java/.ceignore rename to logging/java-structured/.ceignore diff --git a/logging/java/.dockerignore b/logging/java-structured/.dockerignore similarity index 100% rename from logging/java/.dockerignore rename to logging/java-structured/.dockerignore diff --git a/logging/java/.gitignore b/logging/java-structured/.gitignore similarity index 100% rename from logging/java/.gitignore rename to logging/java-structured/.gitignore diff --git a/logging/java/Dockerfile b/logging/java-structured/Dockerfile similarity index 100% rename from logging/java/Dockerfile rename to logging/java-structured/Dockerfile diff --git a/logging/java/pom.xml b/logging/java-structured/pom.xml similarity index 100% rename from logging/java/pom.xml rename to logging/java-structured/pom.xml diff --git a/logging/java-structured/src/main/java/com/ibm/cloud/codeengine/sample/App.java b/logging/java-structured/src/main/java/com/ibm/cloud/codeengine/sample/App.java new file mode 100644 index 000000000..280d21a0c --- /dev/null +++ b/logging/java-structured/src/main/java/com/ibm/cloud/codeengine/sample/App.java @@ -0,0 +1,56 @@ +package com.ibm.cloud.codeengine.sample; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App { + + public static void main(String[] args) { + + Logger logger = LoggerFactory.getLogger("my_logger"); + // Expect to be rendered as INFO level log message + logger.info("This is a structured log message"); + + // Expect to be rendered as DEBUG level log message + logger.debug("This is a structured log message"); + + // Expect to be rendered as WARN level log message + logger.warn("This is a structured log message"); + + // Expect to be rendered as ERROR level log message + logger.error("This is a structured log message"); + + logger.atDebug().addKeyValue("extra_key", "extra_value") + .log("A structured log entry that contains an extra key"); + + // Expect to be rendered as INFO level log message. The additional JSON struct + // is available as a searchable, filterable fields + logger.atInfo() + .addKeyValue("requestId", "some-request-id") + .addKeyValue("userId", "user-123456") + .addKeyValue("action", "test") + .addKeyValue("metadata", Map.of("foo", "bar")) + .log("A structured log entry that carries a ton of additional fields"); + + // Multi-line example. Expect to be rendered in a single log message + logger.atInfo().log(""" + Multi-line log sample: + Line 1: initialization started + Line 2: loading modules + Line 3: modules loaded + Line 4: entering main loop + End of sample"""); + + // Error logging. + // The error stack trace is rendered in a single log message (see field stack_trace) + try { + throw new RuntimeException("boom!"); + } catch (Exception e) { + logger.atError() + .setCause(e) // also sets throwable on the event + .log("An error occurred"); + } + } +} diff --git a/logging/java/src/main/resources/logback.xml b/logging/java-structured/src/main/resources/logback.xml similarity index 84% rename from logging/java/src/main/resources/logback.xml rename to logging/java-structured/src/main/resources/logback.xml index 494c92b31..9dadb1ade 100644 --- a/logging/java/src/main/resources/logback.xml +++ b/logging/java-structured/src/main/resources/logback.xml @@ -3,10 +3,11 @@ UTC - timestamp + [ignore] [ignore] [ignore] [ignore] + [ignore] diff --git a/logging/java-unstructured/.ceignore b/logging/java-unstructured/.ceignore new file mode 100644 index 000000000..9f970225a --- /dev/null +++ b/logging/java-unstructured/.ceignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/logging/java-unstructured/.dockerignore b/logging/java-unstructured/.dockerignore new file mode 100644 index 000000000..ba4a1025a --- /dev/null +++ b/logging/java-unstructured/.dockerignore @@ -0,0 +1,5 @@ +.dockerignore +.gitignore +build +Dockerfile +target diff --git a/logging/java-unstructured/.gitignore b/logging/java-unstructured/.gitignore new file mode 100644 index 000000000..1de565933 --- /dev/null +++ b/logging/java-unstructured/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/logging/java-unstructured/Dockerfile b/logging/java-unstructured/Dockerfile new file mode 100644 index 000000000..fb4348e55 --- /dev/null +++ b/logging/java-unstructured/Dockerfile @@ -0,0 +1,16 @@ +# Download dependencies and compile in builder stage +FROM registry.access.redhat.com/ubi9/openjdk-21 AS builder + +COPY --chown=${UID} . /src +WORKDIR /src +RUN mvn package -Dmaven.test.skip=true + +# Build final stage +FROM gcr.io/distroless/java21 + +COPY --chown=1001:0 --from=builder /src/target/logging-1.0.0-SNAPSHOT.jar /app/logging-1.0.0-SNAPSHOT.jar + +USER 1001:0 +WORKDIR /app + +CMD ["logging-1.0.0-SNAPSHOT.jar"] diff --git a/logging/java-unstructured/pom.xml b/logging/java-unstructured/pom.xml new file mode 100644 index 000000000..c5a65a733 --- /dev/null +++ b/logging/java-unstructured/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + com.ibm.cloud.codeengine.sample + logging + 1.0.0-SNAPSHOT + + logging + https://github.com/IBM/CodeEngine + + + UTF-8 + 21 + + + + + + + + + + + maven-clean-plugin + 3.4.0 + + + + maven-compiler-plugin + 3.13.0 + + + maven-jar-plugin + 3.4.2 + + + + + + + maven-shade-plugin + 3.6.0 + + false + + + com.ibm.cloud.codeengine.sample.App + + + + + + package + + shade + + + + + + + \ No newline at end of file diff --git a/logging/java-unstructured/src/main/java/com/ibm/cloud/codeengine/sample/App.java b/logging/java-unstructured/src/main/java/com/ibm/cloud/codeengine/sample/App.java new file mode 100644 index 000000000..0d168744a --- /dev/null +++ b/logging/java-unstructured/src/main/java/com/ibm/cloud/codeengine/sample/App.java @@ -0,0 +1,46 @@ +package com.ibm.cloud.codeengine.sample; + +import java.time.Instant; + +// This simple Code Engine job demonstrates how to write unstructured log lines +// using the built-in capabilities of Java + +public class App { + + public static void main(String[] args) { + + // expect to be rendered as INFO level log message + System.out.println("This is a unstructured log message without a severity identifier"); + + // expect to be rendered as WARN level log message + System.out.println("This is a unstructured log message with a severity identifier WARN"); + + // expect to be rendered as ERROR level log message, without the keyword ERROR + // being part of the message + System.out.println("ERROR This is a unstructured log message prefixed with the level"); + + // expect to be rendered as INFO level log message, without the timestamp being + // part of the message + System.out + .println(Instant.now().toString() + " This is a unstructured log message prefixed with the timestamp"); + + // expect to be rendered as DEBUG level log message, without the timestamp and + // keyword DEBUG being part of the message + System.out.println(Instant.now().toString() + + " DEBUG This is a unstructured log message prefixed with the timestamp and level"); + + // Multi-line example. Expect to be rendered in a single log message + System.out.println("Multi-line log sample...\\nStep 1: Validating input...\\nStep 2: Processing payment..."); + + // Error logging. + // Expect that the stacktrace is rendered in multiple log statements + // Note: Use structure logs to support multi-line error stack traces + try { + throw new RuntimeException("boom!"); + } catch (Exception e) { + System.err.println("Stacktrace example " + e); // error message + e.printStackTrace(); // full stack trace to stderr + } + + } +} diff --git a/logging/java/build b/logging/java/build deleted file mode 100755 index ff6dfe9a5..000000000 --- a/logging/java/build +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# Env Vars: -# REGISTRY: name of the image registry/namespace to store the images -# NOCACHE: set this to "--no-cache" to turn off the Docker build cache -# -# NOTE: to run this you MUST set the REGISTRY environment variable to -# your own image registry/namespace otherwise the `docker push` commands -# will fail due to an auth failure. Which means, you also need to be logged -# into that registry before you run it. - -set -ex -export REGISTRY=${REGISTRY:-icr.io/codeengine} - -# Build the image -docker build ${NOCACHE} -t ${REGISTRY}/trusted-profiles/java . --platform linux/amd64 - -# And push it -docker push ${REGISTRY}/trusted-profiles/java diff --git a/logging/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java b/logging/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java deleted file mode 100644 index ac89e9678..000000000 --- a/logging/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ibm.cloud.codeengine.sample; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class App { - - public static void main(String[] args) { - - Logger logger = LoggerFactory.getLogger("my_logger"); - logger.info("A simple log message"); - logger.atDebug().addKeyValue("extra_key", "extra_value").log("A structured log entry"); - } -} diff --git a/logging/node-structured/index.mjs b/logging/node-structured/index.mjs index b39a9bd40..e26cf5c9c 100644 --- a/logging/node-structured/index.mjs +++ b/logging/node-structured/index.mjs @@ -1,42 +1,53 @@ import winston from "winston"; const { combine, json } = winston.format; +// This simple Code Engine job demonstrates how to write structured log lines +// using the the external package winston. Other frameworks like bunyan, log4js, pino work similar + const logger = winston.createLogger({ level: "debug", transports: [new winston.transports.Console()], format: combine(json()), }); -logger.info("1.This is a structured log message"); -logger.debug("2.This is a structured log message"); -logger.warn("3.This is a structured log message"); -logger.error("4.This is a structured log message"); +// Expect to be rendered as INFO level log message +logger.info("This is a structured log message"); + +// Expect to be rendered as DEBUG level log message +logger.debug("This is a structured log message"); + +// Expect to be rendered as WARN level log message +logger.warn("This is a structured log message"); + +// Expect to be rendered as ERROR level log message +logger.error("This is a structured log message"); -logger.debug("5. A structured log entry", { +// Expect to be rendered as DEBUG level log message. The extra key is available as a searchable, filterable field +logger.debug("A structured log entry that contains an extra key", { extra_key: "extra_value", }); -logger.info("6. some message that carries a ton of additional fields", { - requestId: crypto.randomUUID(), +// Expect to be rendered as INFO level log message. The additional JSON struct is available as a searchable, filterable fields +logger.info("A structured log entry that carries a ton of additional fields", { + requestId: "some-request-id", userId: "user-123456", action: "test", metadata: { foo: "bar", - timestamp: new Date().toISOString(), }, }); -// Error logging -try { - throw new Error("boom!"); -} catch (err) { - logger.error("7. Error occurred", err); -} - -// Multi-line log sample -logger.info(`8. Multi-line log sample: +// Multi-line example. Expect to be rendered in a single log message +logger.info(`Multi-line log sample: Line 1: initialization started Line 2: loading modules Line 3: modules loaded Line 4: entering main loop End of sample`); + +// Error logging. The error stack trace is rendered in a single log message (see field stack) +try { + throw new Error("boom!"); +} catch (err) { + logger.error("An error occurred", err); +} diff --git a/logging/node-structured/package-lock.json b/logging/node-structured/package-lock.json deleted file mode 100644 index 5136f9a9a..000000000 --- a/logging/node-structured/package-lock.json +++ /dev/null @@ -1,300 +0,0 @@ -{ - "name": "ce-logging", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ce-logging", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "winston": "^3.17.0" - } - }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "license": "MIT", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", - "license": "MIT" - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "license": "MIT", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", - "license": "MIT" - }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", - "license": "MIT" - }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", - "license": "MIT" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT" - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", - "license": "MIT" - }, - "node_modules/logform": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", - "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", - "license": "MIT", - "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "license": "MIT", - "dependencies": { - "fn.name": "1.x.x" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", - "license": "MIT" - }, - "node_modules/triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/winston": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", - "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", - "license": "MIT", - "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.7.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.9.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-transport": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", - "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", - "license": "MIT", - "dependencies": { - "logform": "^2.7.0", - "readable-stream": "^3.6.2", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - } - } -} diff --git a/logging/node-structured/package.json b/logging/node-structured/package.json index fe5edb73f..0171ef832 100644 --- a/logging/node-structured/package.json +++ b/logging/node-structured/package.json @@ -11,6 +11,6 @@ "license": "MIT", "homepage": "https://cloud.ibm.com/containers/serverless", "dependencies": { - "winston": "^3.17.0" + "winston": "^3.19.0" } } \ No newline at end of file diff --git a/logging/node-unstructured/index.mjs b/logging/node-unstructured/index.mjs index 7cde93860..5ff7a5572 100644 --- a/logging/node-unstructured/index.mjs +++ b/logging/node-unstructured/index.mjs @@ -1,16 +1,29 @@ +// This simple Code Engine job demonstrates how to write unstructured log lines +// using the built-in capabilities of Node.js -// Write example unstructured logs -console.log("This is a unstructured log message without a severity identifier"); -console.log("This is a unstructured log message with a severity identifier WARN \n"); -console.warn("This is a unstructured log message using a specific level"); -console.log("ERROR This is a unstructured log message prefixed with the level"); +// Expect to be rendered as INFO level log message +console.log("This is a unstructured log message without a severity identifier"); + +// Expect to be rendered as WARN level log message +console.log("This is a unstructured log message with a severity identifier WARN"); + +// Expect to be rendered as INFO level log message +console.warn("This is a unstructured log message using a specific level"); + +// Expect to be rendered as ERROR level log message, without the keyword ERROR being part of the message +console.log("ERROR This is a unstructured log message prefixed with the level"); + +// Expect to be rendered as INFO level log message, without the timestamp being part of the message console.log(`${new Date().toISOString()} This is a unstructured log message prefixed with the timestamp`); + +// Expect to be rendered as DEBUG level log message, without the timestamp and keyword DEBUG being part of the message console.log(`${new Date().toISOString()} DEBUG This is a unstructured log message prefixed with the timestamp and level`); -// Multi-line example +// Multi-line example. Expect to be rendered in a single log message console.log("Multi-line log sample...\\nStep 1: Validating input...\\nStep 2: Processing payment..."); -// Error logging +// Error logging. Expect that the stacktrace is rendered in multiple log statements +// Note: Use structure logs to support multi-line error stack traces try { throw new Error("boom!"); } catch (err) { diff --git a/logging/python/.dockerignore b/logging/python-structured/.dockerignore similarity index 100% rename from logging/python/.dockerignore rename to logging/python-structured/.dockerignore diff --git a/logging/python/Dockerfile b/logging/python-structured/Dockerfile similarity index 100% rename from logging/python/Dockerfile rename to logging/python-structured/Dockerfile diff --git a/logging/python-structured/main.py b/logging/python-structured/main.py new file mode 100644 index 000000000..894d13245 --- /dev/null +++ b/logging/python-structured/main.py @@ -0,0 +1,87 @@ + +from loguru import logger +import sys +import json +import traceback + +# This simple Code Engine job demonstrates how to write structured log lines +# using the logging library loguru (https://github.com/Delgan/loguru) + +# Define a custom JSON sink +def json_sink(message): + record = message.record + + # Base fields: level + message, no timestamp + payload = { + "level": record["level"].name, # e.g., "INFO" + "message": record["message"], # rendered message + } + + # Merge in any bound extra fields as top-level keys + # (skip reserved keys to avoid accidental overwrite) + for k, v in record["extra"].items(): + if k not in ("level", "message", "stack"): + payload[k] = v + + # If an exception is attached, render full stack trace into "stack" + exc = record["exception"] + if exc: + # exc.type, exc.value, exc.traceback are available from Loguru + stack_text = "".join(traceback.format_exception(exc.type, exc.value, exc.traceback)) + payload["stack"] = stack_text + + # Emit a single JSON line + sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n") + sys.stdout.flush() + + +# Remove default handler (which includes timestamp, etc.) and add our custom sink +logger.remove() +logger.add(json_sink, level="DEBUG") # lowest level you want to capture + + +if __name__ == '__main__': + + # expect to be rendered as INFO level log message + logger.info("This is a structured log message"); + + # expect to be rendered as DEBUG level log message + logger.debug("This is a structured log message"); + + # expect to be rendered as WARN level log message + logger.warning("This is a structured log message"); + + # expect to be rendered as ERROR level log message + logger.error("This is a structured log message"); + + # Expect to be rendered as DEBUG level log message. The extra key is available as a searchable, filterable field + logger.bind(extra_key="extra_value").debug("A structured log entry that contains an extra key") + + # Expect to be rendered as INFO level log message. The additional JSON struct is available as a searchable, filterable fields + logger.bind( + requestId="some-request-id", + userId="user-123456", + action="test", + metadata={"foo": "bar"}, + ).info("A structured log entry that carries a ton of additional fields") + + + # Multi-line example. Expect to be rendered in a single log message + logger.info( + "Multi-line log sample:\n" + "Line 1: initialization started\n" + "Line 2: loading modules\n" + "Line 3: modules loaded\n" + "Line 4: entering main loop\n" + "End of sample" + ) + + # Error logging. The error stack trace is rendered in a single log message (see field stack) + try: + raise RuntimeError("boom!") + except Exception: + # logger.exception() automatically attaches the current exception info + logger.exception("An error occurred") + + + diff --git a/logging/python-structured/requirements.txt b/logging/python-structured/requirements.txt new file mode 100644 index 000000000..7248303e5 --- /dev/null +++ b/logging/python-structured/requirements.txt @@ -0,0 +1 @@ +loguru \ No newline at end of file diff --git a/logging/python-unstructured/.dockerignore b/logging/python-unstructured/.dockerignore new file mode 100644 index 000000000..9a715f53b --- /dev/null +++ b/logging/python-unstructured/.dockerignore @@ -0,0 +1,2 @@ +.dockerignore +Dockerfile \ No newline at end of file diff --git a/logging/python-unstructured/Dockerfile b/logging/python-unstructured/Dockerfile new file mode 100644 index 000000000..de687ace9 --- /dev/null +++ b/logging/python-unstructured/Dockerfile @@ -0,0 +1,18 @@ +# Download dependencies in builder stage +FROM registry.access.redhat.com/ubi9/python-312 AS builder + +COPY requirements.txt . +RUN python -m pip install -r requirements.txt + +# Build final stage +FROM gcr.io/distroless/python3 + +ENV PYTHONPATH=/app/site-packages + +COPY --chown=1001:0 --from=builder /opt/app-root/lib/python3.12/site-packages ${PYTHONPATH} +COPY --chown=1001:0 main.py /app/ + +USER 1001:0 +WORKDIR /app + +CMD ["main.py"] diff --git a/logging/python-unstructured/main.py b/logging/python-unstructured/main.py new file mode 100644 index 000000000..b96de0fb3 --- /dev/null +++ b/logging/python-unstructured/main.py @@ -0,0 +1,31 @@ +# This simple Code Engine job demonstrates how to write unstructured log lines +# using the built-in capabilities of Python + +# Expect to be rendered as INFO level log message +print("This is a unstructured log message without a severity identifier"); + +# Expect to be rendered as WARN level log message +print("This is a unstructured log message with a severity identifier WARN"); + +# Expect to be rendered as ERROR level log message, without the keyword ERROR being part of the message +print("ERROR This is a unstructured log message prefixed with the level"); + +# Expect to be rendered as INFO level log message, without the timestamp being part of the message +from datetime import datetime, timezone +ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" +print(f"{ts} This is an unstructured log message prefixed with the timestamp") + +# Expect to be rendered as DEBUG level log message, without the timestamp and keyword DEBUG being part of the message +print(f"{datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")} DEBUG This is a unstructured log message prefixed with the timestamp and level") + +# Multi-line example. Expect to be rendered in a single log message +print("Multi-line log sample...\\nStep 1: Validating input...\\nStep 2: Processing payment...") + +# Error logging. Expect that the stacktrace is rendered in multiple log statements +# Note: Use structure logs to support multi-line error stack traces +try: + raise Exception("boom!") +except Exception as err: + print("Stacktrace example", err) + import traceback + print(traceback.format_exc(), end="") diff --git a/logging/python-unstructured/requirements.txt b/logging/python-unstructured/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/logging/python/main.py b/logging/python/main.py deleted file mode 100644 index 5482bf261..000000000 --- a/logging/python/main.py +++ /dev/null @@ -1,67 +0,0 @@ -import logging -import json -import uuid -from datetime import datetime, timezone - -# # Create the logger -logger = logging.getLogger("json_logger") -logger.setLevel(logging.DEBUG) - -class JsonFormatter(logging.Formatter): - def format(self, record): - log_entry = { - "timestamp": datetime.now(timezone.utc).isoformat(), # Add UTC timestamp - "level": record.levelname, # Log level (e.g., INFO, DEBUG, ERROR) - "message": record.getMessage() - } - # Add extra fields to the log entry if they exist - if hasattr(record, '__dict__'): - for key, value in record.__dict__.items(): - # Skip standard LogRecord attributes and private attributes - if (key not in ('args', 'asctime', 'created', 'exc_info', 'exc_text', - 'filename', 'funcName', 'id', 'levelname', 'levelno', - 'lineno', 'module', 'msecs', 'message', 'msg', - 'name', 'pathname', 'process', 'processName', - 'relativeCreated', 'stack_info', 'thread', 'threadName', 'taskName') - and not key.startswith('_')): - # Add the extra field to the log entry - log_entry[key] = value - - return json.dumps(log_entry) # Return the log entry as a JSON string - - -# Create a console handler and set the custom JSON formatter -console_handler = logging.StreamHandler() -console_handler.setFormatter(JsonFormatter()) -logger.addHandler(console_handler) - -if __name__ == '__main__': - # Regular messages - logger.info("This is a structured log message") - - # Log a JSON objects (with 'message' key) - logger.debug("A structured log entry", extra={"extra_key": "extra_value"}) - - logger.info("some message that carries a ton of additional fields", extra={ - "requestId": str(uuid.uuid4()), - "userId": "user-123456", - "action": "test", - "metadata": { - "foo": "bar", - "timestamp": datetime.now(timezone.utc).isoformat() - } - }) - - # Logging errors - try: - 1/0 - except Exception as err: - logger.error(f"Error occurred: {err}") - - # Multi-line log sample - logger.info("""📜 Multi-line log sample: -Line 1: initialization started -Line 2: loading modules -Line 3: modules loaded -Line 4: entering main loop -End of sample""") diff --git a/logging/run b/logging/run index fef700f9c..12d3d6468 100755 --- a/logging/run +++ b/logging/run @@ -7,9 +7,12 @@ LANGUAGE="${1:=all}" languages=( node-unstructured node-structured - python - go - java + python-unstructured + python-structured + go-unstructured + go-structured + java-unstructured + java-structured ) for i in "${languages[@]}"; do @@ -26,7 +29,7 @@ for i in "${languages[@]}"; do echo -e "\nUpdating the job '$job_name' ..." fi - ibmcloud ce job $create_or_update --n "$job_name" --src . --context-dir "$i/" --memory 0.5G --cpu 0.25 --wait + ibmcloud ce job $create_or_update --n "$job_name" --src . --context-dir "$i/" --memory 0.5G --cpu 0.25 --wait --retrylimit 0 echo "Submit a job run for $i ..." jobrun_name="$job_name-$(openssl rand -hex 6)" From 49bbd596062a99dce9609f3bea698e26618e2b89 Mon Sep 17 00:00:00 2001 From: Enrico Regge Date: Mon, 2 Mar 2026 00:45:46 +0100 Subject: [PATCH 7/8] removed the thread_name field from the ignore list --- logging/java-structured/src/main/resources/logback.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/logging/java-structured/src/main/resources/logback.xml b/logging/java-structured/src/main/resources/logback.xml index 9dadb1ade..7bc191421 100644 --- a/logging/java-structured/src/main/resources/logback.xml +++ b/logging/java-structured/src/main/resources/logback.xml @@ -7,7 +7,6 @@ [ignore] [ignore] [ignore] - [ignore] From 89f97776a2e6260ae116e35f1a9cc0d671547cff Mon Sep 17 00:00:00 2001 From: Enrico Regge Date: Mon, 2 Mar 2026 09:29:14 +0100 Subject: [PATCH 8/8] bumped dependencies --- logging/java-structured/pom.xml | 6 +++--- logging/python-structured/requirements.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/logging/java-structured/pom.xml b/logging/java-structured/pom.xml index c4f5355de..d7e1f9bed 100644 --- a/logging/java-structured/pom.xml +++ b/logging/java-structured/pom.xml @@ -25,17 +25,17 @@ ch.qos.logback logback-classic - 1.5.18 + 1.5.32 ch.qos.logback logback-core - 1.5.18 + 1.5.32 net.logstash.logback logstash-logback-encoder - 8.1 + 9.0 diff --git a/logging/python-structured/requirements.txt b/logging/python-structured/requirements.txt index 7248303e5..91405a63b 100644 --- a/logging/python-structured/requirements.txt +++ b/logging/python-structured/requirements.txt @@ -1 +1 @@ -loguru \ No newline at end of file +loguru~=0.7.3 \ No newline at end of file