Attempting to create a universal logger factory using a functional approach

After reading Martin Fowler's blog on domain oriented observability, I was inspired to implement this concept without relying on classes. However, I encountered some challenges involving generics in TypeScript. Here is the snippet of my code (the error occurs towards the end):

import { Obj } from '../model/types';

interface Logger {

}

interface RouterLogger extends Logger {
    requestReceived: () => void
}

interface ExecutorLogger extends Logger {
    apiFound: () => void,
    templateLoaded: () => void
    apiExecuted: () => void
    apiRunEnded: () => void
}

interface TemplaterLogger extends Logger {
    templateRequestFired: () => void
    respondedSuccessfully: () => void
    respondedWithError: () => void
}

interface ApiLogger extends Logger {
    apiRequestFired: () => void,
    respondedSuccessfully: () => void,
    respondedWithError: () => void,
}

const loggers = {
    router: ({ data, metadata }: Obj): RouterLogger => ({
        requestReceived: () => {
            console.log('Request Received', data, metadata)
            // some rabbitMQ messaging
            // etc
        },
    }),
    executor: (context: Obj): ExecutorLogger => ({
        apiFound: () => { },
        templateLoaded: () => { },
        apiExecuted: () => { },
        apiRunEnded: () => { },
    }),
    templater: (context: Obj): TemplaterLogger => ({
        templateRequestFired: () => { },
        respondedSuccessfully: () => { },
        respondedWithError: () => { },
    }),
    externalApi: (context: Obj): ApiLogger => ({
        apiRequestFired: () => { console.log('request sent', context) },
        respondedSuccessfully: (response) => { console.log('success', context, response) },
        respondedWithError: () => { },
    })
}

function getLoggerDispenser<T extends Logger>(domain: keyof typeof loggers) {
    return (context: Obj): T => loggers[domain](context); //'RouterLogger' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Logger'
}

export {
    Logger,
    RouterLogger,
    ExecutorLogger,
    ApiLogger,
    getLoggerDispenser,
}

The issue I am facing regarding exporting generic Loggers seems apparent - there must be a more efficient way to achieve this. Any guidance in finding a solution would be greatly appreciated.

EDIT: Please note that I am utilizing partial application when obtaining the logger, enabling me to initialize the logger with a context for simplified logging once all necessary data is available.

EDIT 2 (refer to the updated code above for an example using the context): An instance where this setup would come into play:

const externalApiLogger = getLoggerDispenser('externalApi')({ jobId: 1, issuerId: 34 })

// send request
externalApiLogger.apiRequestFired()
// read response = success
externalApiLogger.respondedSuccessfully(response)

Answer №1

Here's a suggestion to enhance the getLoggerDispenser() function by making it generic for its domain input parameter:

function getLoggerDispenser<K extends keyof typeof loggers>(domain: K) {
    return loggers[domain];
}
getLoggerDispenser("executor")(obj).apiFound(); // okay
getLoggerDispenser("router")(obj).apiFound(); // error

This modification preserves the functionality of your original implementation, as loggers[domain] always returns a one-argument function that handles an Obj input and produces a specific Logger instance. Using

context => loggers[domain](context)
is essentially equivalent unless there are multi-parameter functions involved, which is not the case in this scenario. If there are distinctions between loggers[domain] and
context => loggers[domain](context)
, consider adjusting the example code to illustrate these variations with a minimum reproducible example.

If you opt to modify the function to return

(context) => loggers[domain](context)
, note that the compiler may not determine the exact return type needed to identify the logger correctly. In such cases, utilize a type assertion to enforce strong typing:

function getLoggerDispenser<K extends keyof typeof loggers>(domain: K) {
    return (context: Obj) => loggers[domain](context) as ReturnType<typeof loggers[K]>;
}

getLoggerDispenser("executor")(obj).apiFound(); // okay
getLoggerDispenser("router")(obj).apiFound(); // error

These adjustments maintain the intended behavior.


Hope this explanation proves helpful. Best of luck!

Playground link to code

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Cypress mistakenly looks for cypress.config.js instead of .ts and attempts to find a file in an incorrect directory

Working within a NX mono repo, I am running component tests for an Angular app using Cypress. However, I keep encountering an error in the cypress app that occurs when a test reruns after making changes to the testing code: Your configFile threw an error f ...

How to Add a Rule to an Existing Application Load Balancer Listener using AWS CDK

When I inherited a project, I discovered an application load balancer with a HTTPS Listener that was set up before I began using CDK. This listener currently has 13 rules in place that route requests based on hostname to different fargate instances, with ...

React: Issue accessing URL parameters using useParams() within a nested component

In my demo application, there are two components - QuoteDetail and Comments. Both require URL parameters, but I am only able to access them in the parent component. App.tsx: <Switch> // ... <Route path="/quotes" exact> <Al ...

Creating custom observables by utilizing ViewChildren event and void functions in Angular 12: A step-by-step guide

I am currently working on developing a typeahead feature that triggers a service call on keyup event as the user types in an input field. The challenge I face is that my input field is enclosed within an *ngIf block, which requires me to utilize ViewChildr ...

Leveraging Array.map within Angular Interpolation

Is it possible to achieve the following in HTML using Angular 2+? {{ object.array.map((o) => o.property ) }} Whenever I try to execute this code, it crashes the Angular application. Do I need to utilize Pipes or any other technique? ...

Angular 9 Singleton Service Issue: Not Functioning as Expected

I have implemented a singleton service to manage shared data in my Angular application. The code for the service can be seen below: import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class DataS ...

What is the best way to divide text into key-value pairs using JavaScript?

I have data in text format from my Raspberry Pi that I need to insert into MongoDB as key-pair values or JSON for my Node.js Application. I'm relatively new to JavaScript and I'm looking for a solution. Any suggestions would be greatly appreciate ...

Traverse the JSON object list retrieved from the ASP.NET webservice

I have a web service in ASP.NET that returns a list of generics (List'<'Construct>) serialized as JSON using the following code: [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1) ...

Guide on embedding a module into another module

I created a component called barchar. There is another module component located at src\app\modules\dashboard\page and this file contains the module. However, barchzt does not have the module. How can I utilize barchzt in the src&bsol ...

Access the most up-to-date information through the API URL

Objective: Whenever the 'Test' Button is clicked, new data must be fetched from the backend using the API link and displayed on the modal form. Issue: If text in the input box is changed or deleted, then the modal is closed and the 'Tes ...

Prevent special characters in input fields using Angular and TypeScript

I am currently working on an Angular project using Ant Design ng Zorro. I've encountered an issue with validation when trying to restrict special characters in an input field. Despite setting up the validation rules, it seems that the key press event ...

Struggling to connect the array of objects from the .ts file with the template (.html) in Angular

Inside this .ts file, I am populating the "mesMenus" array that I want to display in the .html file: export class MenusComponent{ mesMenus= new Array<Menu>(); constructor(private gMenuService:GestionMenuService){ this.gMenuService.onAdd ...

Managing onChange in a ReactJs project

Currently, my React tsx page features input boxes like the following: <textarea value={this.state.myData!.valueOne} onChange={(e) => this.handleValueOneChange(e)}/> <textarea value={this.state.myData!.valueTwo} onChange={(e) => thi ...

Restoring previous configuration in Ionic2 from the resume() lifecycle method

Encountering an issue with my ionic2 application where I save the last state in local storage when the app goes to the background. Upon resuming, it checks for the value of lastState in local storage and pushes that state if a value exists. The specific er ...

Is it correct to implement an interface with a constructor in TypeScript using this method?

I am completely new to TypeScript (and JavaScript for the most part). I recently came across the article discussing the differences between the static and instance sides of classes in the TypeScript handbook. It suggested separating the constructor into an ...

Handling errors in nested asynchronous functions in an express.js environment

I am currently developing a microservice that sends messages to users for phone number verification. My focus is on the part of the microservice where sending a message with the correct verification code will trigger the addition of the user's phone n ...

Automatically adjust padding in nested lists with ReactJS and MaterialUI v1

How can I automatically add padding to nested lists that may vary in depth due to recursion? Currently, my output looks like this: https://i.stack.imgur.com/6anY9.png: However, I would like it to look like this instead: https://i.stack.imgur.com/dgSPB. ...

Issue with rendering images retrieved from JSON data

Struggling with displaying images in my Ionic and Angular pokedex app. The JSON file data service pulls the image paths, but only displays the file path instead of the actual image. Any ideas on what might be causing this issue? Sample snippet from the JS ...

Adjust the size of an event in the Angular Full Calendar with Chronofy, while utilizing constraints to control the drag and drop functionality

I'm currently in the process of developing an availability calendar for scheduling meetings during open times. If a time slot is unavailable, users should not be able to place an event there. While I have successfully implemented this feature, I am ...

What causes TypeScript to overlook the generic constraint within a function?

Here is a simple illustration of what I am trying to convey: type Shape = 'square' | 'circle'; type Params<S extends Shape> = S extends 'square' ? { side: number } : { radius: number }; function getArea<S ...