0

There's a new context manager on the block. with died quietly, so naturally it's time to re-brand and revive :)

Setup: Looking at the new using keyword I immediately found a scenario where it would be a really great pattern. Google Cloud Platform (GCP) has log(), warn() and error() for its cloud functions. And often times I need to run them locally first though, and some of the logs are pretty extensive. Instead of looking through the terminal window, I would want to import my own implementation of those functions from a helper file keeping the implementation syntax as is. This would allow me to have .log files for debugging and testing in a local folder.

I've been trying to accomplish it like this:

// localLogger.ts

import { createWriteStream } from "fs"
import { fileURLToPath } from "url"
import { dirname, join } from "path"

export function wrapLogs(
  callee: string,
  type: "info" | "warn" | "error" = "info"
) {
  const logs: {
    when: string
    logs: string
    payload?: any
  }[] = []
  const __importFile = fileURLToPath(import.meta.url)
  const __logFileDir = dirname(__importFile)
  const __logFile = join(__logFileDir, "../logs", `${type}.log`)
  const stream = createWriteStream(__logFile, {
    flags: "a",
    encoding: "utf8",
  })
  return function (input?: string, payload: any = {}) {
    if (input) {
      const timeStamp = new Date().toISOString().split("T")[1].slice(0, -1)
      logs.push({ when: timeStamp, logs: input })
      if (Object.keys(payload).length) {
        logs[logs.length - 1].payload = payload
      }
    }
    return {
      callee,
      logs,
      done: () => {
        logs.forEach((log) => {
          stream.write(`${log.when} - [${callee}] - ${type}: ${log.logs}\n`)
          if (log.payload) {
            stream.write(`payload: ${JSON.stringify(log.payload, null, 2)}\n`)
          }
        })
        stream.on("finish", () => {
          console.log("done")
        })
        stream.end()
      },
    }
  }
}

this comes close, but no cigar. This implementation requires running the .done() method at the end of each function which implements the loggers - less than ideal, since that leaves a lot of room for forgetful mistakes. This is where using should theoretically come in.

// implementer.ts
import { wrapLogs } from './localLogger.ts'

function myCloudFuntion(){
 const log = wrapLogs('myCloudFunction')
 // does something and then logs
 const data = { payload: 'relevant data' }
 log("some useful log here", data)
 // more stuff
 log().done() // <- this part i would like to do without
 return
}

So in the ideal world i would like to define loggers like so:

// localLogger.ts

...
// last return of the wrapLogs definition

    return {
      callee,
      logs,
      [Symbol.dispose]() {
        logs.forEach((log) => {
          stream.write(`${log.when} - [${callee}] - ${type}: ${log.logs}\n`)
          if (log.payload) {
            stream.write(`payload: ${JSON.stringify(log.payload, null, 2)}\n`)
          }
        })
        stream.on("finish", () => {
          console.log("done")
        })
        stream.end()
      },
    }

And in the implementing files:

// implementer.ts

function myCloudFunction(){
  using log = wrapLogs("myCloudFunction")
  //...
  log("some message", data)
  return  // <- no more log().done()
}

Unfortunately this results in all sorts of issues.

Any help is welcome.

These are my initial steps into monad land and the using as well, so be gentle :)

EDIT: i've created a repo which should make the setup less time-consuming https://github.com/kolchurinvv/logger-replacements

3
  • What sorts of issues are you getting? Commented Oct 13, 2023 at 11:39
  • If you can provide a minimal reproducible example that can be pasted into a single file in a standalone IDE without reliance on libraries external to TS (e.g., something suitable for the TypeScript Playground) you'll probably get more help here. "Minimal" also means that we likely don't need to see the internals of your timestamp manipulations, log file locations, etc... anything you're not asking about should be removed so it's not distracting. Right now I'm not inclined to wade too far into this because of the setup time/effort needed to begin. Commented Oct 13, 2023 at 14:55
  • I hear you. I was thinking by providing more context it would help. in that spirit i've created a repo which should make the process of setup less time-consuming. adding it to the question Commented Oct 14, 2023 at 13:46

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.