Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implementing logging #7

Open
rush-levint opened this issue Jun 14, 2019 · 9 comments
Open

Implementing logging #7

rush-levint opened this issue Jun 14, 2019 · 9 comments
Labels
question Further information is requested

Comments

@rush-levint
Copy link

Hi, how do we go about implementing logging? Do we just import a logger function anywhere in our use cases or pass it down from the frameworks & drivers layer?

@arcdev1
Copy link
Contributor

arcdev1 commented Jun 15, 2019

Create an adapter that wraps some framework and then inject it into your use case.

On a brand new project I usually start by wrapping the console API and introduce some logging framework later as we get closer to launch. Something like:

// logger/logger.js
export default function makeLogger(spec) {
  return Object.freeze({
    debug: console.debug,
    error: console.error,
    info: console.info,
    warn: console.warn
  })
}
// logger/index.js
import logger from './logger'
const logger = makeLogger()
export default logger

@Ryuno-Ki
Copy link

Hm, I'd pass some kind of argument to allow a prefix.
From experience, I can tell, that this helps to identify, from where logging happened.
Plus, if you log an error, the stack trace might be confusing …

You could use loglevel or winston for example.
The other parts of the application shouldn't care, what solution is used in the end …

@rush-levint
Copy link
Author

rush-levint commented Jun 17, 2019

Looks clearer now, thanks :D

Considering @Ryuno-Ki 's opinion, would it still be valid if in logger/index.js we expose the logger as a function accepting a prefix that returns a (child) logger where the prefix is taken into account?

something similar to loglevel's .getLogger(name)

@arcdev1
Copy link
Contributor

arcdev1 commented Jun 17, 2019

There are many ways to extend/improve on the basic example I provided. It really depends on the needs of your application.

The main point is, use an adapter and injection to avoid a direct dependency on a specific logging library. Logging is something that will get used everywhere so you have to be very careful about directly referencing a particular 3rd party library all over your code.

@arcdev1 arcdev1 added the question Further information is requested label Jun 30, 2019
@PixsaOJ
Copy link

PixsaOJ commented Mar 2, 2021

Could you provide an example with singleton logger that uses 3rd party library like Bunyan?

@arcdev1
Copy link
Contributor

arcdev1 commented Mar 7, 2021

@PixsaOJ it might look something like this...

// /logger/logger.js
export function makeLogger({thirdPartyLogger}) {
  return Object.freeze({
    info: (msg) => thirdPartyLogger.info(msg) // you should do more here (e.g. wrap proprietary errors).
   // more method wrappers like .warn or .error
  })
}
// /logger/index.js
import ThirdPartyLogger from "third-party-logger" // this could be Bunyan
import {makeLogger} from "./logger"

const thirdPartyLogger = new ThirdPartyLogger({/* config */})
export const Logger = makeLogger({thirdPartyLogger}) // here's your singleton

Now the rest of your app uses the logger exported from /logger/index.js and if you ever want or need to switch logging libraries you have one place to do it.

@PixsaOJ
Copy link

PixsaOJ commented Mar 7, 2021

Thanks for the response @arcdev1. What do you think, would it be a good idea to use our generated logger as dependency injection? Or just require/import it whenever we need to log.

E.g. when we are writing tests, it could be easier to inject as dependency. However, I do not know how pretty it would be to that in actual code. I would have to inject in routes first, then in controllers, then in use-cases...

EDIT: I currently have it as a global variable global.logger = config.logger()

@arcdev1
Copy link
Contributor

arcdev1 commented Mar 10, 2021

@PixsaOJ Logging tends to have side effects (e.g. writing to a file) so it's important to use dependency injection to avoid brittle tests.

Reading between the lines of your question, it seems like you are logging in lots of places. If you find that you get a lot of value from doing that then it's all good.

In my case, I've noticed that limiting logs to an exception handler seems to be enough. So, at the entry point(s) of my app, I have a single try/catch that sends Exceptions to an Exception Handler which logs the exception and makes some decisions about what else to do with the Exception based on its type. This means I only really have to inject the Logger in my Exception Handler.

I've worked for clients that like to log a lot more than that and in those cases, we just accepted that you have to inject your logger everywhere.

@PixsaOJ
Copy link

PixsaOJ commented Mar 10, 2021

@arcdev1 Makes sense, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants