Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

LoggerInterface #60

Merged
merged 1 commit into from

22 participants

Jordi Boggiano Lukas Kahwe Smith Sebastiaan Stok Paul Dragoonis Rob Loach Matthieu Napoli Igor Makarov Matthew Lanigan Hidayet Doğan Crell Evgeniy OZ Asmir Mustafic Niklas Lindgren Phil Sturgeon Drak Markus Staab Paul M. Jones Till! Lars Strojny Johannes Eugene Leonovich Jordan Stout
Jordi Boggiano

You can view the rendered version at https://github.com/Seldaek/fig-standards/blob/logger-interface/proposed/logger-interface.md

The psr/log package that contains all the interfaces/classes/traits can be found at https://github.com/php-fig/log

proposed/logger-interface.md
((44 lines not shown))
+
+ // MAY produce "User Bob created" or store
+ // "User %username% created" + a context array for later use
+ $logger->log('User %username% created', array('username' => $username));
+ ```
+
+ Implementors MAY use placeholders to implement various escaping strategies
+ and translate logs for display. As such their use is very much encouraged.
+
+ Placeholders pointing to a context value that does not exist MAY trigger
+ an E_STRICT notice. Users SHOULD always provide the data matching the
+ placeholders they use.
+
+[RFC 5424]: http://tools.ietf.org/html/rfc5424
+
+- A `PSR\Log\NoopLogger` is provided together with the interface. It MAY be

the class is called NullLogger

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Lukas Kahwe Smith

I hope that in the final form the interface/class will actually be provided as a composer package. Furthermore I would expect a set of tests to validate compliance.

Jordi Boggiano Seldaek referenced this pull request in symfony/symfony
Closed

LoggerInterface and Monolog #5968

Jordi Boggiano

I will happily provide a repo with the interface as a composer package of course. A test suite as well should not be too hard.

Lukas Kahwe Smith

I think it would be good to setup such a repo now and then move it to this organization once the proposal is accepted.

proposed/logger-interface.md
@@ -0,0 +1,258 @@
+Logger Interface
+================
+
+This document describes a common interface for logging libraries.
+
+The main goal is to allow libraries to receive a `PSR\Log\LoggerInterface`

Should the namespace not be Psr?

// cc @drak

Jordi Boggiano
Seldaek added a note

PSR-0/1/2 don't specify a namespace case I think. I don't really mind which it is, but I would find PSR more natural since that's how it's always written. I will take the issue to the ML.

Drak
drak added a note

I know we're taking about the namespace and not the class, but it makes no sense, and is quite confusing if we mandate that you must have CamelCasedClasses even for acronyms like SqlFactory and then have a free for all on namespaces. It's inconsistent and therefor ugly. We should set an example by naming it Psr.

Matthew Lanigan
rintaun added a note

There is nothing in the PSRs which limits the casing of Vendor Names. Relevant portions:

  • Each namespace must have a top-level namespace ("Vendor Name"). [PSR-0]
  • Alphabetic characters in vendor names, namespaces, and class names may be of any combination of lower case and upper case. [PSR-0]
  • This means each class is in a file by itself, and is in a namespace of at least one level: a top-level vendor name. [PSR-1]

No PSR after PSR-0 limits the formatting of Vendor Names, so only the qualification of "may be any combination of lower case and upper case" exists. As such, I believe PSR is the best Vendor Name for use in PSRs.

Drak
drak added a note

That is technically correct, but it's not actually the point. Acronymns in classes must be CamelCased, so SQL must be written as Sql. It will be confusing and irritating if we set an example that it's OK to capitalise acronyms in the namespace and not in the class naming. It doesn't have to be a rule, just set by example and it makes things a lot easier for adoption since there is consistency in the flow. FYI we chose not to impose the rule on the namespace because PSR-0 had already been out for a long time and some had adopted all lowercase vendor names. As a result, imposing a rule on namespace would have meant either those projects would have to create a BC break in order to adopt PSR1/2 or comply with only part. The best compromise was to just leave the vendor naming alone. This does not mean we should not set an example that is consistent with the naming of classes. DrakPHP\Dumper\PhpDumper for example is going to look stupid because of the inconsistency. For this reason, let's keep camel casing standards consistent within our code examples.

Matthew Lanigan
rintaun added a note

There is no mention of acronyms in any PSR; the only thing stated is "Class names MUST be declared in StudlyCaps" (which is never defined). No examples include acronyms.

Drak
drak added a note

@rintaun - the use of StudyCaps specifically covers how classes are formatted. SQL is not StudlyCaps. Yes there are different interpretations of what StudyCaps means but that is why there is an express example to make it clear: Class names MUST be declared in StudlyCaps. where StudlyCaps has been written by example to show what it means: STudLYCaps is therefor not valid because we have defined the example specifically.

Matthew Lanigan
rintaun added a note

@drak Except none of those examples include an acronym; so the prescription is ambiguous at best.

Drak
drak added a note

@rintaun - with all due respect, we didn't compile the PSR wording expecting to fight a court battle over it. If we were to be so exhaustive the PSRs would be unreadable and confusing to just about everyone who wrote it. Legal language requires wording that is super verbose. But read over the discussions, we were always trying to use examples so we didn't have to be overly-verbose and lawyer/legalistic about it. I can't really say more than this. This is really about trying to give some consistency.

Matthew Lanigan
rintaun added a note

@drak I do agree that a standard should not be unreadable or confusing; with all due respect, the existing PSRs have clearly failed in this. There have been a number of emails on the list regarding ambiguity and this is simply another instance of such. Essentially, in an attempt to avoid being overly-verbose, the exact opposite has occurred: the standards as they exist are underly-verbose, leaving too much open to interpretation. It should not be necessary to consult a massive amount of email discussion in order to determine the proper interpretation of a standard; if this had occurred with HTTP, I believe it is safe to say that the web would not exist as it does today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Paul Dragoonis
Owner
proposed/logger-interface.md
((27 lines not shown))
+- Every method accepts a string as the message. Objects are allowed so long as
+ they implement a __toString() method. Implementors MUST cast the message to
+ a string.
+
+- Every method accepts an array as context data. This is meant to hold any
+ extraneous information that does not fit well in a string. The array can
+ contain anything. Implementors MUST ensure they treat context data with
+ as much lenience as possible. A given value in the context MUST NOT throw
+ an exception or trigger a php `E_ERROR`, `E_WARNING`, ...
+
+- If an `Exception` object is passed in the context data, it MUST be in the
+ `'exception'` key. Logging exceptions is a common pattern and this allows
+ implementors to extract a back trace from the exception when the log
+ backend in use supports it.
+
+- The message MAY contain placeholders in `%variable%` format (e.g. `%foo%`).

How is % escaped?
Are deep replacements supported now (e.g. %foo.bar%), which would kind of prevent a . in keys?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
proposed/logger-interface.md
((12 lines not shown))
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
+"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
+interpreted as described in [RFC 2119][].
+
+[RFC 2119]: http://tools.ietf.org/html/rfc2119
+
+1. Specification
+-----------------
+
+- The interface exposes methods to write logs to the eight [RFC 5424][] levels
+ (debug, info, notice, warning, error, critical, alert, emergency).
+ Implementors MAY support custom levels but they are not allowed via this
+ interface.
+
+- Every method accepts a string as the message. Objects are allowed so long as

Is it useful to pass an object if implementors MUST cast it to a string anyway? If someone passes an object (which I do not expect as any implementor is forced to treat it like a string anyway), then he can also cast it to a string himself.

Matthew Lanigan
rintaun added a note

The difference is that the language as is places the onus on a significantly smaller number of individuals (library developers), and as such requires significantly less actual total work.

Jordi Boggiano
Seldaek added a note

That is one thing, but the main advantage is extensibility I think. Doing it this way kind of allows you to pass an object to an extended interface and basic implementations will just cast it instead of choking on it.

Jordi Boggiano
Seldaek added a note

I don't agree. That means that as a user you can pass it anything and it should cope, but even if an implementation casts to string it doesn't protect it from failing on non-strings. Casting a string that does not implement __toString yields: Catchable fatal error: Object of class $class could not be converted to string.

Maybe my formulation was also not ideal. What I mean is something like this

Every method accepts a string, or an object with a __toString method as message. Implementors MAY have special handling for the passed object in which case they MAY forgo casting the object. In all other cases, implementors MUST cast the message to a string before proceeding.

That is what is intended here, no? The difference simply is that casting is optional (depending on the object), but not required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Jordi Boggiano

I took care of the remaining feedback, added log() and the level constants, renamed to Psr because I would tend to agree with @drak even though I don't like it.

proposed/logger-interface.md
((19 lines not shown))
+1. Specification
+-----------------
+
+- The interface exposes eight methods to write logs to the eight [RFC 5424][]
+ levels (debug, info, notice, warning, error, critical, alert, emergency).
+
+- A ninth method, `log`, accepts a log level as first argument. Calling this
+ method with one of the log level constants MUST have the same result as
+ calling the level-specific method. Calling this method with a level not
+ defined by this specification MUST throw a `Psr\Log\InvalidArgumentException`
+ if the implementation does not know about the level. Users SHOULD NOT use a
+ custom level without knowing for sure the current implementation supports it.
+
+- Every method accepts a string as the message, or an object with a
+ `__toString()` method. Implementors MAY have special handling for the passed
+ objects. If that is not the case, implementors MUST cast it to a string.
Crell
Crell added a note

It's not entirely clear here if implementors means someone writing a logging class that implements this interface, or someone calling an object that implements this interface. I assume the intent is "$message must be a string or something we can treat as a string, beyond that, meh."

Possible revised language:

Every method accepts a string as the message, or an object with a '__toString()' method. Implementations of this interface MAY allow for special handling for passed objects, but MUST support strings and passed objects that only implement __toString().

(I think that makes it clearer that if you write a Logger class, it must allow for __toString() objects. Anything else is out of scope.)

Jordi Boggiano
Seldaek added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
proposed/logger-interface.md
((65 lines not shown))
+ context the data will be displayed.
+
+ There is no support for deep replacement of placeholders. Placeholder names
+ match directly to the keys of the context array. Here is a sample
+ implementation to make it clear:
+
+ ```php
+ $message = 'User %username% created';
+ $context = array('username' => $username);
+
+ foreach ($context as $key => $val) {
+ $message = str_replace('%'.$key.'%', $val, $message);
+ }
+ ```
+
+- A `Psr\Log\NullLogger` is provided together with the interface. It MAY be
Crell
Crell added a note

Just flagging this as a line to be updated once we sort out the namespace question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
proposed/logger-interface.md
((44 lines not shown))
+ implementors to extract a stack trace from the exception when the log
+ backend supports it. Implementors MUST still verify that the `'exception'`
+ key is actually an `Exception` before using it as such, as it MAY contain
+ anything.
+
+- The message MAY contain placeholders in `%variable%` format (e.g. `%foo%`).
+ Those placeholders MAY be replaced by their corresponding value in the
+ context array. See for example:
+
+ ```php
+ $logger = new Logger();
+ $username = 'Bob';
+
+ // MAY produce "User Bob created" or store
+ // "User %username% created" + a context array for later use
+ $logger->log('User %username% created', array('username' => $username));
Eugene Leonovich
rybakit added a note

I guess it should be $logger->log(LoggerInterface::INFO, ...) or $logger->info(...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Matthieu Napoli

What about #57? Has it been validated that PHP-FIG will distribute code/interfaces? (which I'm all for btw)

Lars Strojny lstrojny commented on the diff
proposed/logger-interface.md
((278 lines not shown))
+ public function setLogger(LoggerInterface $logger);
+}
+```
+
+5. `Psr\Log\LogLevel`
+---------------------
+
+```php
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes log levels
+ */
+class LogLevel

Should this be final?

Jordi Boggiano
Seldaek added a note

Makes it look more like an enum.

Jordi Boggiano
Seldaek added a note

But it prevents people from extending it to add levels without having to duplicate all the existing levels.

Igor Makarov
omgnull added a note

Please, dont make Java from PHP.

Matthieu Napoli
mnapoli added a note

@omgnull wth?

Igor Makarov
omgnull added a note

Creating new classes to wrap every primitive type it's java like style. Why the LoggerInterface self can't contain this constants? PHP frameworks grows very fast, and consuming more and more memory. And one more thing, we already have defined log constants (http://php.net/manual/en/function.syslog.php) whats the need of dups?

Also I don't see good thing in using log types as strings. In example I need to store logs for periods of time, size of logs is prefered, so I decide to store logs type as integer.

Matthieu Napoli
mnapoli added a note

good point regarding existing PHP constants (which are also integers)

Jordi Boggiano
Seldaek added a note

This was split off to another class because some log libraries already have those constants defined, which prevents them from implementing the interface. As for why they are not integers, it's again because integers can conflict with existing level constants of log libraries. Strings can't conflict since they map to their own meaning.

Igor Makarov
omgnull added a note

Before this all the time I think liibraries depends on and implements interfaces. Now I see, I was wrong :)

Jordi Boggiano
Seldaek added a note

Not sure what you are saying, but what I meant was maybe not clear. If the constants were on the interface, it would prevent it from being implemented by libraries that also define the same constants (unless they break BC, which isn't nice for adoption of the PSR). Now that the constants are split off, it's possible to implement the LoggerInterface anywhere. We need to compromise and take the existing ecosystem into account if we are to create successful PSRs.

Paul M. Jones Owner
pmjones added a note

"take the existing ecosystem into account if we are to create successful PSRs." Hear hear. :-)

Igor Makarov
omgnull added a note

I'm sure BC is not the problem if we want to create really usefull things, support old thing always expensive. As I said before, the size and performance is impotant. I have a project with billions of logs for analyzing purpose, and additional payment for useless bytes looks bad.

Jordi Boggiano
Seldaek added a note
Till!
till added a note

@Seldaek Don't existing libraries have to break BC anyway when they want to add this interface? The string vs int argument seems invalid then.

Jordi Boggiano
Seldaek added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Matthieu Napoli mnapoli commented on the diff
proposed/logger-interface.md
((95 lines not shown))
+- Every method accepts an array as context data. This is meant to hold any
+ extraneous information that does not fit well in a string. The array can
+ contain anything. Implementors MUST ensure they treat context data with
+ as much lenience as possible. A given value in the context MUST NOT throw
+ an exception nor raise any php error, warning or notice.
+
+- If an `Exception` object is passed in the context data, it MUST be in the
+ `'exception'` key. Logging exceptions is a common pattern and this allows
+ implementors to extract a stack trace from the exception when the log
+ backend supports it. Implementors MUST still verify that the `'exception'`
+ key is actually an `Exception` before using it as such, as it MAY contain
+ anything.
+
+### 1.4 Helper classes and interfaces
+
+- The `Psr\Log\AbstractLogger` class lets you implement the `LoggerInterface`
Matthieu Napoli
mnapoli added a note

Is the AbstractLogger class somewhere? or to be written?

Same question for NullLogger, LoggerTrait and LoggerAwareTrait.

Jordi Boggiano
Seldaek added a note

All those are in the package at https://github.com/Seldaek/log/ - it will be migrated to php-fig/log in due time.

Matthieu Napoli
mnapoli added a note

Ok thanks, I know that these are trivial but I just wanted to know if they would be included in the PSR in the end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Matthieu Napoli

With the proposed interface, parameterized logging is possible with something like:

$logger->debug("User {user} created in {group}", 
    array(
        'user' => $user->getName(),
        'group' => $group->getName(),
    )
);

I find it quite verbose and restrictive.

There are alternatives for the same result:

// Allow numeric keys:
$logger->debug("User {user} created in {group}", array($user->getName(), $group->getName()));

// No context array, undefinite number of parameters
$logger->debug("User {user} created in {group}", $user->getName(), $group->getName());

// No need for string keys
$logger->debug("User {} created in {}", $user->getName(), $group->getName());

After all, the last solution is perfectly functional (used by slf4j (java) for example: http://slf4j.org/faq.html#logging_performance).

Jordi Boggiano

@mnapoli if you want the long story please check the mailing list archives, but the short version is that numeric keys are allowed {0} {1} works fine for your first example, the second does replacement but does not handle the use case of storing arbitrary data attached to the message. The last one is handled by my first example.

Igor Makarov

Sorry, but I dont feel well with some this standarts, creating a slow fat monster from simple logger, no, let me quit. I really like KISS.

// Push
$logger->debug(sprintf("User %s created in %s", $user->getName(), $group->getName()), $args);

// If need format logs, I must set normalizer directly
$logger->setNormalizer($logNormalizer);
Matthew Lanigan

@omgnull Please read the extensive discussion on the mailing list; the issues on which you are commenting have been discussed at length there prior to this standard reaching a vote.

Igor Makarov

@rintaun Had already. We can easy imagine issues in any situation. Logger must be simple and extesible for specific needs. Most of universal tools are bad.

Hidayet Doğan

Why don't you just follow placeholder styles using in PDO already? Lots of people (which uses PDO) is already familiar with that style. (ie, named placeholders and question mark placeholders).

Disclaimer: I'm not a member of PHP-FIG group nor framework/big open source software author/collaborator but long term PHP user (~13 years). Just want to know reasons for selecting curly braces for parameter placeholders...

// named placeholders
$logger->debug("User :user created in :group", array($user->getName(), $group->getName()));
// question mark placeholders
$logger->debug("User ? created in ?", array($user->getName(), $group->getName()));
Crell

@omgnull And yet a standard that is too basic is completely useless. We went over this repeatedly on the FIG list and reached the same conclusion: Placeholders are necessary, and they have to be standardized to be useful.

@hdogan We wanted a placeholder format with a character on both sides to simplify processing. Anything we chose would have been at least partially arbitrary. Curly braces are easy to read, easy to parse (different start/end delimiters), and are what is used by the HTTP pattern IETF draft. We've also discussed an HTTP client PSR proposal, and if we get back to that and need placeholders there then {} would be the natural choice. Using them here, too, provides some likely consistency between future-PSRs.

And positional array placeholders are useless. Remember, inserting placeholders into the string will more often than not happen MUCH LATER, on display in some log viewer backed by SQL, Mongo, Redis, or whatever. Syslog is the only case where you'd be merging them immediately. You need that separate context later as it makes processing vastly simpler; it also makes the code more self-documenting.

Matthieu Napoli

@Seldaek my bad, I thought the PR was the place to discuss the choices. I'll join the mailing list for such things. And thanks for taking the time to explain.

Till! till commented on the diff
proposed/logger-interface.md
((152 lines not shown))
+ * The message MAY contain placeholders in the form: {foo} where foo
+ * will be replaced by the context data in key "foo".
+ *
+ * The context array can contain arbitrary data, the only assumption that
+ * can be made by implementors is that if an Exception instance is given
+ * to produce a stack trace, it MUST be in a key named "exception".
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
+ * for the full interface specification.
+ */
+interface LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
Till!
till added a note

You guys sure people only log strings?

Jordi Boggiano
Seldaek added a note

The spec allows for objects that implement __toString() - other stuff should go in context

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Till! till commented on the diff
proposed/logger-interface.md
((156 lines not shown))
+ * can be made by implementors is that if an Exception instance is given
+ * to produce a stack trace, it MUST be in a key named "exception".
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
+ * for the full interface specification.
+ */
+interface LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function emergency($message, array $context = array());
Till!
till added a note

Why emergency and not the usual keywords?

Jordi Boggiano
Seldaek added a note
Till!
till added a note

I also think context is not within the scope of this interface.

Jordan Stout
j added a note

:thumbsup:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Evgeniy OZ

I've changed my opinion after some time of thinking. I now think placeholders are really necessary. For case if some value for placeholder will contain too long string.

Crell

It's also insecure and untranslatable. This was discussed at great length before it was included in the spec, and we determined that it was a necessity.

Matthieu Napoli

@omgnull It is also very bad for performances, read http://slf4j.org/faq.html#logging_performance and see how parameterized messages are the way to go.

Igor Makarov

@mnapoli thanks, you made me laugh, see https://github.com/Seldaek/fig-standards/blob/logger-interface/proposed/logger-interface.md interpolate function it's concat all placeholders with each curly brace, and look at my first comment, it's all going to be another ugly slow java, but in case of php it's much slower.

Matthieu Napoli

@omgnull you didn't even read my link right? The point is not about the speed of the interpolate function VS sprintf.

The point is that in production, all you debug() statements should not be processed and concatenate strings and all because they are not logged. Processing them is a waste of resources.

Read http://slf4j.org/faq.html#logging_performance for more information.

Igor Makarov

@mnapoli no, read it before, and I suggest this LoggerInterface not only for debug purpose

Matthieu Napoli

@omgnull that's not what I'm saying. LoggerInterface is not for debug purposes only.

However, $logger->debug("Hello {0}", ["foobar"]); will not concatenate the strings if you are in production. sprintf will, and this will be useless (because in production debug level is usually not enabled).

Also, if your logger persists the data not processed (i.e. it will log "Hello {0}" and array(0 => "foobar") separately), it's even better because no string concatenation will ever happen when you application runs. Afterwards, with a log viewer application, you can process the messages and read the string "Hello foobar".

=> This format allows to save resources.

Igor Makarov

@mnapoli I'm also do not mean always to use sprintf, it's only for example. I have a production service that give away advertising for other company services and accumulate logs. Load average for the service is 40 000 logs per second, in future planing is about 100k p/s. My Log struct is the Level, Message and array of context (about 5 items in each context) values. If I store as is and interpolate in Viewer it will require more storage space. Sorry, but I see nothing better.

Jordi Boggiano

@omgnull if you have very specific requirements IMO it's alright to get off the "best practices" and optimize where needed. In your application code if it fits your use case better to use sprintf nobody will prevent you from doing so. This PSR itself doesn't even say you shouldn't use it. It just says for placeholders there is something available, and it is nice to use it for frameworks that need it for escaping/translation. If you can't or don't want to use them, just don't.

Asmir Mustafic

Hi! Why not use integers inside LogLevel instead of strings?
Using integers should be possible "filter" logs with bitmasks in a very useful way, using same techniques used for error_log inside PHP...

Niklas Lindgren

Why does the package contain an empty extending class \Psr\Log\InvalidArgumentException? What was wrong with using \InvalidArgumentException directly?

Phil Sturgeon
Owner

This has been accepted. @dragoonis @pmjones can one of you guys hit merge?

Drak

The website needs to be updated too.

Phil Sturgeon
Owner

I was about to update the website when I noticed this hadn't been merged, that's why im bumping this.

Drak

More voting members need to have administrative access to the github PHP-FIG organisation.

Jordi Boggiano

@nikcorg some argued that it was better because it allows you to catch only the exception coming from the logger. It doesn't change much either way.

@goetas this was discussed on list a few times, but the short version is that integers can lead to conflicts (and hence require BC breaks) with current logging libs, while those strings with the level names are safe.

Asmir Mustafic

@Seldaek Thanks for the reply. Please consider my opinion.

Paul Dragoonis dragoonis merged commit ad3cad5 into from
Markus Staab

Well done guys! :+1:

Paul Dragoonis
Owner
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 13, 2012
  1. Jordi Boggiano

    Add LoggerInterface Proposal

    Seldaek authored
This page is out of date. Refresh to see the latest.
Showing with 304 additions and 0 deletions.
  1. +304 −0 proposed/logger-interface.md
304 proposed/logger-interface.md
View
@@ -0,0 +1,304 @@
+Logger Interface
+================
+
+This document describes a common interface for logging libraries.
+
+The main goal is to allow libraries to receive a `Psr\Log\LoggerInterface`
+object and write logs to it in a simple and universal way. Frameworks
+and CMSs that have custom needs MAY extend the interface for their own
+purpose, but SHOULD remain compatible with this document. This ensures
+that the third-party libraries an application uses can write to the
+centralized application logs.
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
+"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
+interpreted as described in [RFC 2119][].
+
+The word `implementor` in this document is to be interpreted as someone
+implementing the `LoggerInterface` in a log-related library or framework.
+Users of loggers are refered to as `user`.
+
+[RFC 2119]: http://tools.ietf.org/html/rfc2119
+
+1. Specification
+-----------------
+
+### 1.1 Basics
+
+- The `LoggerInterface` exposes eight methods to write logs to the eight
+ [RFC 5424][] levels (debug, info, notice, warning, error, critical, alert,
+ emergency).
+
+- A ninth method, `log`, accepts a log level as first argument. Calling this
+ method with one of the log level constants MUST have the same result as
+ calling the level-specific method. Calling this method with a level not
+ defined by this specification MUST throw a `Psr\Log\InvalidArgumentException`
+ if the implementation does not know about the level. Users SHOULD NOT use a
+ custom level without knowing for sure the current implementation supports it.
+
+[RFC 5424]: http://tools.ietf.org/html/rfc5424
+
+### 1.2 Message
+
+- Every method accepts a string as the message, or an object with a
+ `__toString()` method. Implementors MAY have special handling for the passed
+ objects. If that is not the case, implementors MUST cast it to a string.
+
+- The message MAY contain placeholders which implementors MAY replace with
+ values from the context array.
+
+ Placeholder names MUST correspond to keys in the context array.
+
+ Placeholder names MUST be delimited with a single opening brace `{` and
+ a single closing brace `}`. There MUST NOT be any whitespace between the
+ delimiters and the placeholder name.
+
+ Placeholder names SHOULD be composed only of the characters `A-Z`, `a-z`,
+ `0-9`, underscore `_`, and period `.`. The use of other characters is
+ reserved for future modifications of the placeholders specification.
+
+ Implementors MAY use placeholders to implement various escaping strategies
+ and translate logs for display. Users SHOULD NOT pre-escape placeholder
+ values since they can not know in which context the data will be displayed.
+
+ The following is an example implementation of placeholder interpolation
+ provided for reference purposes only:
+
+ ```php
+ /**
+ * Interpolates context values into the message placeholders.
+ */
+ function interpolate($message, array $context = array())
+ {
+ // build a replacement array with braces around the context keys
+ $replace = array();
+ foreach ($context as $key => $val) {
+ $replace['{' . $key . '}'] = $val;
+ }
+
+ // interpolate replacement values into the the message and return
+ return strtr($message, $replace);
+ }
+
+ // a message with brace-delimited placeholder names
+ $message = "User {username} created";
+
+ // a context array of placeholder names => replacement values
+ $context = array('username' => 'bolivar');
+
+ // echoes "Username bolivar created"
+ echo interpolate($message, $context);
+ ```
+
+### 1.3 Context
+
+- Every method accepts an array as context data. This is meant to hold any
+ extraneous information that does not fit well in a string. The array can
+ contain anything. Implementors MUST ensure they treat context data with
+ as much lenience as possible. A given value in the context MUST NOT throw
+ an exception nor raise any php error, warning or notice.
+
+- If an `Exception` object is passed in the context data, it MUST be in the
+ `'exception'` key. Logging exceptions is a common pattern and this allows
+ implementors to extract a stack trace from the exception when the log
+ backend supports it. Implementors MUST still verify that the `'exception'`
+ key is actually an `Exception` before using it as such, as it MAY contain
+ anything.
+
+### 1.4 Helper classes and interfaces
+
+- The `Psr\Log\AbstractLogger` class lets you implement the `LoggerInterface`
Matthieu Napoli
mnapoli added a note

Is the AbstractLogger class somewhere? or to be written?

Same question for NullLogger, LoggerTrait and LoggerAwareTrait.

Jordi Boggiano
Seldaek added a note

All those are in the package at https://github.com/Seldaek/log/ - it will be migrated to php-fig/log in due time.

Matthieu Napoli
mnapoli added a note

Ok thanks, I know that these are trivial but I just wanted to know if they would be included in the PSR in the end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ very easily by extending it and implementing the generic `log` method.
+ The other eight methods are forwarding the message and context to it.
+
+- Similarly, using the `Psr\Log\LoggerTrait` only requires you to
+ implement the generic `log` method. Note that since traits can not implement
+ interfaces, in this case you still have to `implement LoggerInterface`.
+
+- The `Psr\Log\NullLogger` is provided together with the interface. It MAY be
+ used by users of the interface to provide a fall-back "black hole"
+ implementation if no logger is given to them. However conditional logging
+ may be a better approach if context data creation is expensive.
+
+- The `Psr\Log\LoggerAwareInterface` only contains a
+ `setLogger(LoggerInterface $logger)` method and can be used by frameworks to
+ auto-wire arbitrary instances with a logger.
+
+- The `Psr\Log\LoggerAwareTrait` trait can be used to implement the equivalent
+ interface easily in any class. It gives you access to `$this->logger`.
+
+- The `Psr\Log\LogLevel` class holds constants for the eight log levels.
+
+2. Package
+----------
+
+The interfaces and classes described as well as relevant exception classes
+and a test suite to verify your implementation are provided as part of the
+[php-fig/psr-log](https://packagist.org/packages/php-fig/psr-log) package.
+
+3. `Psr\Log\LoggerInterface`
+----------------------------
+
+```php
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger instance
+ *
+ * The message MUST be a string or object implementing __toString().
+ *
+ * The message MAY contain placeholders in the form: {foo} where foo
+ * will be replaced by the context data in key "foo".
+ *
+ * The context array can contain arbitrary data, the only assumption that
+ * can be made by implementors is that if an Exception instance is given
+ * to produce a stack trace, it MUST be in a key named "exception".
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
+ * for the full interface specification.
+ */
+interface LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
Till!
till added a note

You guys sure people only log strings?

Jordi Boggiano
Seldaek added a note

The spec allows for objects that implement __toString() - other stuff should go in context

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * @param array $context
+ * @return null
+ */
+ public function emergency($message, array $context = array());
Till!
till added a note

Why emergency and not the usual keywords?

Jordi Boggiano
Seldaek added a note
Till!
till added a note

I also think context is not within the scope of this interface.

Jordan Stout
j added a note

:thumbsup:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function alert($message, array $context = array());
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function critical($message, array $context = array());
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function error($message, array $context = array());
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function warning($message, array $context = array());
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function notice($message, array $context = array());
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function info($message, array $context = array());
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function debug($message, array $context = array());
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function log($level, $message, array $context = array());
+}
+```
+
+4. `Psr\Log\LoggerAwareInterface`
+---------------------------------
+
+```php
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger-aware instance
+ */
+interface LoggerAwareInterface
+{
+ /**
+ * Sets a logger instance on the object
+ *
+ * @param LoggerInterface $logger
+ * @return null
+ */
+ public function setLogger(LoggerInterface $logger);
+}
+```
+
+5. `Psr\Log\LogLevel`
+---------------------
+
+```php
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes log levels
+ */
+class LogLevel

Should this be final?

Jordi Boggiano
Seldaek added a note

Makes it look more like an enum.

Jordi Boggiano
Seldaek added a note

But it prevents people from extending it to add levels without having to duplicate all the existing levels.

Igor Makarov
omgnull added a note

Please, dont make Java from PHP.

Matthieu Napoli
mnapoli added a note

@omgnull wth?

Igor Makarov
omgnull added a note

Creating new classes to wrap every primitive type it's java like style. Why the LoggerInterface self can't contain this constants? PHP frameworks grows very fast, and consuming more and more memory. And one more thing, we already have defined log constants (http://php.net/manual/en/function.syslog.php) whats the need of dups?

Also I don't see good thing in using log types as strings. In example I need to store logs for periods of time, size of logs is prefered, so I decide to store logs type as integer.

Matthieu Napoli
mnapoli added a note

good point regarding existing PHP constants (which are also integers)

Jordi Boggiano
Seldaek added a note

This was split off to another class because some log libraries already have those constants defined, which prevents them from implementing the interface. As for why they are not integers, it's again because integers can conflict with existing level constants of log libraries. Strings can't conflict since they map to their own meaning.

Igor Makarov
omgnull added a note

Before this all the time I think liibraries depends on and implements interfaces. Now I see, I was wrong :)

Jordi Boggiano
Seldaek added a note

Not sure what you are saying, but what I meant was maybe not clear. If the constants were on the interface, it would prevent it from being implemented by libraries that also define the same constants (unless they break BC, which isn't nice for adoption of the PSR). Now that the constants are split off, it's possible to implement the LoggerInterface anywhere. We need to compromise and take the existing ecosystem into account if we are to create successful PSRs.

Paul M. Jones Owner
pmjones added a note

"take the existing ecosystem into account if we are to create successful PSRs." Hear hear. :-)

Igor Makarov
omgnull added a note

I'm sure BC is not the problem if we want to create really usefull things, support old thing always expensive. As I said before, the size and performance is impotant. I have a project with billions of logs for analyzing purpose, and additional payment for useless bytes looks bad.

Jordi Boggiano
Seldaek added a note
Till!
till added a note

@Seldaek Don't existing libraries have to break BC anyway when they want to add this interface? The string vs int argument seems invalid then.

Jordi Boggiano
Seldaek added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+{
+ const EMERGENCY = 'emergency';
+ const ALERT = 'alert';
+ const CRITICAL = 'critical';
+ const ERROR = 'error';
+ const WARNING = 'warning';
+ const NOTICE = 'notice';
+ const INFO = 'info';
+ const DEBUG = 'debug';
+}
+```
Something went wrong with that request. Please try again.