Skip to content

Commit ed65f22

Browse files
authored
refactor sdk/api logging (#1105)
send errors/warnings/etc by default through PHP's error_log. This gives more control to administrators, since error_log can be configured to write to any stream (file, stderr, etc). I think it's also less surprising for people trying out otel (particularly with development PHP settings, where trigger_error often breaks an application). Adding a configuration value to control where logs go: OTEL_PHP_LOG_DESTINATION.
1 parent b660f27 commit ed65f22

11 files changed

+207
-50
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\API\Behavior\Internal\LogWriter;
6+
7+
class ErrorLogWriter implements LogWriterInterface
8+
{
9+
public function write($level, string $message, array $context): void
10+
{
11+
error_log(Formatter::format($level, $message, $context));
12+
}
13+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\API\Behavior\Internal\LogWriter;
6+
7+
class Formatter
8+
{
9+
public static function format($level, string $message, array $context): string
10+
{
11+
$exception = (array_key_exists('exception', $context) && $context['exception'] instanceof \Throwable)
12+
? $context['exception']
13+
: null;
14+
if ($exception) {
15+
$message = sprintf(
16+
'OpenTelemetry: [%s] %s [exception] %s%s%s',
17+
$level,
18+
$message,
19+
$exception->getMessage(),
20+
PHP_EOL,
21+
$exception->getTraceAsString()
22+
);
23+
} else {
24+
//get calling location, skipping over trait, formatter etc
25+
$caller = debug_backtrace()[3];
26+
$message = sprintf(
27+
'OpenTelemetry: [%s] %s in %s(%s)',
28+
$level,
29+
$message,
30+
$caller['file'],
31+
$caller['line'],
32+
);
33+
}
34+
35+
return $message;
36+
}
37+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\API\Behavior\Internal\LogWriter;
6+
7+
interface LogWriterInterface
8+
{
9+
public function write($level, string $message, array $context): void;
10+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\API\Behavior\Internal\LogWriter;
6+
7+
class NoopLogWriter implements LogWriterInterface
8+
{
9+
public function write($level, string $message, array $context): void
10+
{
11+
//do nothing
12+
}
13+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\API\Behavior\Internal\LogWriter;
6+
7+
use Psr\Log\LoggerInterface;
8+
9+
class Psr3LogWriter implements LogWriterInterface
10+
{
11+
private LoggerInterface $logger;
12+
13+
public function __construct(LoggerInterface $logger)
14+
{
15+
$this->logger = $logger;
16+
}
17+
18+
public function write($level, string $message, array $context): void
19+
{
20+
$this->logger->log($level, $message, $context);
21+
}
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\API\Behavior\Internal\LogWriter;
6+
7+
class StreamLogWriter implements LogWriterInterface
8+
{
9+
private $stream;
10+
11+
public function __construct(string $destination)
12+
{
13+
$stream = fopen($destination, 'a');
14+
if ($stream) {
15+
$this->stream = $stream;
16+
} else {
17+
throw new \RuntimeException(sprintf('Unable to open %s for writing', $destination));
18+
}
19+
}
20+
21+
public function write($level, string $message, array $context): void
22+
{
23+
fwrite($this->stream, Formatter::format($level, $message, $context));
24+
}
25+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\API\Behavior\Internal;
6+
7+
use OpenTelemetry\API\Behavior\Internal\LogWriter\ErrorLogWriter;
8+
use OpenTelemetry\API\Behavior\Internal\LogWriter\LogWriterInterface;
9+
use OpenTelemetry\API\Behavior\Internal\LogWriter\NoopLogWriter;
10+
use OpenTelemetry\API\Behavior\Internal\LogWriter\Psr3LogWriter;
11+
use OpenTelemetry\API\Behavior\Internal\LogWriter\StreamLogWriter;
12+
use OpenTelemetry\API\Globals;
13+
use OpenTelemetry\API\LoggerHolder;
14+
15+
class LogWriterFactory
16+
{
17+
private const OTEL_PHP_LOG_DESTINATION = 'OTEL_PHP_LOG_DESTINATION';
18+
19+
public function create(): LogWriterInterface
20+
{
21+
$dest = Globals::configurationResolver()->getEnum(self::OTEL_PHP_LOG_DESTINATION);
22+
//we might not have an SDK, so attempt to get from environment
23+
if (!$dest) {
24+
$dest = array_key_exists(self::OTEL_PHP_LOG_DESTINATION, $_SERVER)
25+
? $_SERVER[self::OTEL_PHP_LOG_DESTINATION]
26+
: getenv(self::OTEL_PHP_LOG_DESTINATION);
27+
}
28+
if (!$dest) {
29+
$dest = ini_get(self::OTEL_PHP_LOG_DESTINATION);
30+
}
31+
$logger = LoggerHolder::get();
32+
33+
switch ($dest) {
34+
case 'none':
35+
return new NoopLogWriter();
36+
case 'stderr':
37+
return new StreamLogWriter('php://stderr');
38+
case 'stdout':
39+
return new StreamLogWriter('php://stdout');
40+
case 'psr3':
41+
if ($logger) {
42+
return new Psr3LogWriter($logger);
43+
}
44+
error_log('OpenTelemetry: cannot use OTEL_PHP_LOG_DESTINATION=psr3 without providing a PSR-3 logger');
45+
//default to error log
46+
return new ErrorLogWriter();
47+
case 'error_log':
48+
return new ErrorLogWriter();
49+
default:
50+
if ($logger) {
51+
return new Psr3LogWriter($logger);
52+
}
53+
54+
return new ErrorLogWriter();
55+
}
56+
}
57+
}

Behavior/Internal/Logging.php

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44

55
namespace OpenTelemetry\API\Behavior\Internal;
66

7+
use OpenTelemetry\API\Behavior\Internal\LogWriter\LogWriterInterface;
78
use Psr\Log\LogLevel;
89

910
/**
10-
* Logging utility functions for default (error_log) logging.
11+
* Logging utility functions for internal logging (of OpenTelemetry errors/warnings etc).
1112
* This is not part of SDK configuration to avoid creating a dependency on SDK from any package which does logging.
1213
* @todo this should be `@internal`, but deptrac is not happy with that.
1314
*/
1415
class Logging
1516
{
16-
private const VARIABLE_NAME = 'OTEL_LOG_LEVEL';
17+
private const OTEL_LOG_LEVEL = 'OTEL_LOG_LEVEL';
1718
private const DEFAULT_LEVEL = LogLevel::INFO;
1819
private const NONE = 'none';
1920
private const LEVELS = [
@@ -32,6 +33,19 @@ class Logging
3233
* The minimum log level. Messages with lower severity than this will be ignored.
3334
*/
3435
private static ?int $logLevel = null;
36+
private static ?LogWriterInterface $writer = null;
37+
38+
public static function setLogWriter(LogWriterInterface $writer): void
39+
{
40+
self::$writer = $writer;
41+
}
42+
43+
public static function logWriter(): LogWriterInterface
44+
{
45+
self::$writer ??= (new LogWriterFactory())->create();
46+
47+
return self::$writer;
48+
}
3549

3650
/**
3751
* Get level priority from level name
@@ -53,30 +67,13 @@ public static function logLevel(): int
5367
return self::$logLevel;
5468
}
5569

56-
/**
57-
* Map PSR-3 levels to error_log levels.
58-
* Note that we should never use higher than E_USER_WARNING so that we do not break user applications.
59-
*/
60-
public static function map(string $level)
61-
{
62-
switch ($level) {
63-
case LogLevel::WARNING:
64-
case LogLevel::ERROR:
65-
case LogLevel::CRITICAL:
66-
case LogLevel::EMERGENCY:
67-
return E_USER_WARNING;
68-
default:
69-
return E_USER_NOTICE;
70-
}
71-
}
72-
7370
private static function getLogLevel(): int
7471
{
75-
$level = array_key_exists(self::VARIABLE_NAME, $_SERVER)
76-
? $_SERVER[self::VARIABLE_NAME]
77-
: getenv(self::VARIABLE_NAME);
72+
$level = array_key_exists(self::OTEL_LOG_LEVEL, $_SERVER)
73+
? $_SERVER[self::OTEL_LOG_LEVEL]
74+
: getenv(self::OTEL_LOG_LEVEL);
7875
if (!$level) {
79-
$level = ini_get(self::VARIABLE_NAME);
76+
$level = ini_get(self::OTEL_LOG_LEVEL);
8077
}
8178
if (!$level) {
8279
$level = self::DEFAULT_LEVEL;
@@ -88,5 +85,6 @@ private static function getLogLevel(): int
8885
public static function reset(): void
8986
{
9087
self::$logLevel = null;
88+
self::$writer = null;
9189
}
9290
}

Behavior/LogsMessagesTrait.php

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace OpenTelemetry\API\Behavior;
66

77
use OpenTelemetry\API\Behavior\Internal\Logging;
8-
use OpenTelemetry\API\LoggerHolder;
98
use Psr\Log\LogLevel;
109

1110
trait LogsMessagesTrait
@@ -17,33 +16,10 @@ private static function shouldLog(string $level): bool
1716

1817
private static function doLog(string $level, string $message, array $context): void
1918
{
20-
$logger = LoggerHolder::get();
21-
if ($logger !== null) {
19+
$writer = Logging::logWriter();
20+
if (self::shouldLog($level)) {
2221
$context['source'] = get_called_class();
23-
$logger->log($level, $message, $context);
24-
} elseif (self::shouldLog($level)) {
25-
$exception = (array_key_exists('exception', $context) && $context['exception'] instanceof \Throwable)
26-
? $context['exception']
27-
: null;
28-
if ($exception) {
29-
$message = sprintf(
30-
'%s: %s%s%s',
31-
$message,
32-
$exception->getMessage(),
33-
PHP_EOL,
34-
$exception->getTraceAsString()
35-
);
36-
} else {
37-
//get calling location, skipping over trait
38-
$caller = debug_backtrace()[1];
39-
$message = sprintf(
40-
'%s(%s): %s',
41-
$caller['file'],
42-
$caller['line'],
43-
$message,
44-
);
45-
}
46-
trigger_error($message, Logging::map($level));
22+
$writer->write($level, $message, $context);
4723
}
4824
}
4925

Instrumentation/ConfigurationResolverInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ public function getString(string $name): ?string;
1111
public function getBoolean(string $name): ?bool;
1212
public function getInt(string $name): ?int;
1313
public function getList(string $name): array;
14+
public function getEnum(string $name): ?string;
1415
}

0 commit comments

Comments
 (0)