agent/native/ext/log.cpp (694 lines of code) (raw):

/* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "log.h" #include <stdio.h> #include <stdarg.h> #ifndef PHP_WIN32 # include <syslog.h> # include <unistd.h> # include <fcntl.h> #endif #include "elastic_apm_clock.h" #include "util.h" #include "elastic_apm_alloc.h" #include "platform.h" #include "TextOutputStream.h" #include "Tracer.h" #define ELASTIC_APM_CURRENT_LOG_CATEGORY ELASTIC_APM_LOG_CATEGORY_LOG #ifndef PHP_WIN32 LogLevel g_elasticApmDirectLogLevelSyslog = logLevel_info; #endif // #ifndef PHP_WIN32 LogLevel g_elasticApmDirectLogLevelStderr = logLevel_error; String logLevelNames[numberOfLogLevels] = { [logLevel_off] = "OFF", [logLevel_critical] = "CRITICAL", [logLevel_error] = "ERROR", [logLevel_warning] = "WARNING", [logLevel_info] = "INFO", [logLevel_debug] = "DEBUG", [logLevel_trace] = "TRACE" }; const char* logLevelToName( LogLevel level ) { if ( ELASTIC_APM_IS_IN_END_EXCLUDED_RANGE( logLevel_off, level, numberOfLogLevels ) ) { return logLevelNames[ level ]; } return "UNKNOWN"; } enum { loggerMessageBufferSize = 1000 * 1000 + 1 }; // 2020-02-15 21:51:32.123456+02:00 [ERROR] [Ext-Infra] [lifecycle.c:482] [sendEventsToApmServer] Couldn't connect to server blah blah blah blah blah blah blah blah | PID: 12345 | TID: 67890 // 2020-02-15 21:51:32.123456+02:00 [WARNING] [Configuration] [ConfigManager.c:45] [constructSnapshotUsingDefaults] Not found blah blah blah blah blah blah blah blah | PID: 12345 | TID: 67890 // 2020-02-15 21:51:32.123456+02:00 [CRITICAL] [PHP-Bootstrap] [BootstrapShutdownHelper.php:123] [constructSnapshotUsingDefaults] Send failed. Error message: Couldn't connect to server. server_url: `http://localhost:8200' | PID: 123 | TID: 345 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // ^ ^ ^ ^ ^ ^ // ^ ^ ^ ^ ^ Message text // ^ ^ ^ ^ function name // ^ ^ ^ file name:line number // ^ ^ ^ category // ^ level (wrapped in [] and padded with spaces on the right to 10 chars) // timestamp (no padding) // 2020-02-15 21:51:32.123456+02:00 | ERROR | 12345:67890 | lifecycle.c:482 | sendEventsToApmServer | Couldn't connect to server blah blah blah blah blah blah blah blah // 2020-02-15 21:51:32.123456+02:00 | WARNING | 12345:67890 | ConfigManager.c:45 | constructSnapshotUsingDefaults | Not found blah blah blah blah blah blah blah blah // 2020-02-15 21:51:32.123456+02:00 | CRITICAL | 345: 345 | BootstrapShutdownHelper.php:123 | constructSnapshotUsingDefaults | Send failed. Error message: Couldn't connect to server. server_url: `http://localhost:8200' // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // ^ ^ ^ ^ ^ ^ ^ // ^ ^ ^ ^ ^ ^ Message text // ^ ^ ^ ^ ^ function name (padded with spaces on the right to 30 chars) // ^ ^ ^ ^ file name:line number (padded with spaces on the right to 35 chars) // ^ ^ ^ thread ID (padded with spaces on the left to 5 chars) - included only if ZTS is defined // ^ ^ process ID (padded with spaces on the left to 5 chars) // ^ level (padded with spaces on the right to 8 chars) // timestamp (no padding) static const char logLinePartsSeparator[] = " "; static void appendSeparator( TextOutputStream* txtOutStream ) { ELASTIC_APM_ASSERT_VALID_PTR_TEXT_OUTPUT_STREAM( txtOutStream ); streamStringView( ELASTIC_APM_STRING_LITERAL_TO_VIEW( logLinePartsSeparator ), txtOutStream ); } #define ELASTIC_APM_LOG_FMT_PAD_START "" #define ELASTIC_APM_LOG_FMT_NUMBER_PAD_START( minWidth ) \ "%" ELASTIC_APM_LOG_FMT_PAD_START ELASTIC_APM_PP_STRINGIZE( minWidth ) "d" static void streamPadding( Int64 paddingLength, TextOutputStream* txtOutStream ) { if ( paddingLength <= 0 ) return; ELASTIC_APM_REPEAT_N_TIMES( ((size_t)paddingLength) ) streamChar( ' ', txtOutStream ); } static void appendLevel( LogLevel level, TextOutputStream* txtOutStream ) { ELASTIC_APM_ASSERT_VALID_PTR_TEXT_OUTPUT_STREAM( txtOutStream ); const char* posBeforeWrite = textOutputStreamGetFreeSpaceBegin( txtOutStream ); streamChar( '[', txtOutStream ); if ( level < numberOfLogLevels ) { // if it's a level with a name streamPrintf( txtOutStream, "%s", logLevelToName( level ) ); } else { // otherwise print it as a number streamPrintf( txtOutStream, "%d", level ); } streamChar( ']', txtOutStream ); const char* posBeforeAfter = textOutputStreamGetFreeSpaceBegin( txtOutStream ); const int minWidth = 10; streamPadding( minWidth - ( posBeforeAfter - posBeforeWrite ), txtOutStream ); } static void appendCategory( StringView category, TextOutputStream* txtOutStream ) { ELASTIC_APM_ASSERT_VALID_PTR_TEXT_OUTPUT_STREAM( txtOutStream ); streamChar( '[', txtOutStream ); streamStringView( category, txtOutStream ); streamChar( ']', txtOutStream ); } static void appendProcessThreadIds( TextOutputStream* txtOutStream ) { // [PID: 12345] [TID: 67890] ELASTIC_APM_ASSERT_VALID_PTR_TEXT_OUTPUT_STREAM( txtOutStream ); pid_t processId = getCurrentProcessId(); streamStringView( ELASTIC_APM_STRING_LITERAL_TO_VIEW("[PID: "), txtOutStream ); streamPrintf( txtOutStream, "%u", processId ); streamChar( ']', txtOutStream ); appendSeparator( txtOutStream ); pid_t threadId = getCurrentThreadId(); streamStringView( ELASTIC_APM_STRING_LITERAL_TO_VIEW("[TID: "), txtOutStream ); streamPrintf( txtOutStream, "%u", threadId ); streamChar( ']', txtOutStream ); } static void appendFileNameLineNumberPart( StringView filePath, UInt lineNumber, TextOutputStream* txtOutStream ) { // lifecycle.c:482 ELASTIC_APM_ASSERT_VALID_PTR_TEXT_OUTPUT_STREAM( txtOutStream ); streamChar( '[', txtOutStream ); streamStringView( extractLastPartOfFilePathStringView( filePath ), txtOutStream ); streamChar( ':', txtOutStream ); streamPrintf( txtOutStream, "%u", lineNumber ); streamChar( ']', txtOutStream ); } static void appendFunctionName( StringView funcName, TextOutputStream* txtOutStream ) { ELASTIC_APM_ASSERT_VALID_PTR_TEXT_OUTPUT_STREAM( txtOutStream ); streamChar( '[', txtOutStream ); streamStringView( funcName, txtOutStream ); streamChar( ']', txtOutStream ); } StringView buildCommonPrefix( LogLevel statementLevel , StringView category , StringView filePath , UInt lineNumber , StringView funcName , char* buffer , size_t bufferSize ) { // 2020-05-08 08:18:54.154244+02:00 [PID:12345] [TID:12345] [DEBUG] [Configuration] [ConfigManager.c:1127] [ensureConfigManagerHasLatestConfig] Current configuration is already the latest [TransactionId: xyz] [namespace: Impl\AutoInstrument] // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TextOutputStream txtOutStream = makeTextOutputStream( buffer, bufferSize ); // We don't need terminating '\0' after the prefix because we return it as StringView txtOutStream.autoTermZero = false; TextOutputStreamState txtOutStreamStateOnEntryStart; if ( ! textOutputStreamStartEntry( &txtOutStream, &txtOutStreamStateOnEntryStart ) ) { return ELASTIC_APM_STRING_LITERAL_TO_VIEW( ELASTIC_APM_TEXT_OUTPUT_STREAM_NOT_ENOUGH_SPACE_MARKER ); } streamCurrentLocalTime( &txtOutStream ); appendSeparator( &txtOutStream ); appendProcessThreadIds( &txtOutStream ); appendSeparator( &txtOutStream ); appendLevel( statementLevel, &txtOutStream ); appendSeparator( &txtOutStream ); appendCategory( category, &txtOutStream ); appendSeparator( &txtOutStream ); appendFileNameLineNumberPart( filePath, lineNumber, &txtOutStream ); appendSeparator( &txtOutStream ); appendFunctionName( funcName, &txtOutStream ); appendSeparator( &txtOutStream ); textOutputStreamEndEntry( &txtOutStreamStateOnEntryStart, &txtOutStream ); return textOutputStreamContentAsStringView( &txtOutStream ); } StringView insertPrefixAtEachNewLine( Logger* logger , StringView sinkSpecificPrefix , StringView commonPrefix , StringView oldMessage , size_t maxSizeForNewMessage ) { ELASTIC_APM_ASSERT_VALID_PTR( logger->auxMessageBuffer ); ELASTIC_APM_ASSERT_LE_UINT64( maxSizeForNewMessage, loggerMessageBufferSize ); TextOutputStream txtOutStream = makeTextOutputStream( logger->auxMessageBuffer, maxSizeForNewMessage ); // We don't need terminating '\0' after the prefix because we return it as StringView txtOutStream.autoTermZero = false; TextOutputStreamState txtOutStreamStateOnEntryStart; if ( ! textOutputStreamStartEntry( &txtOutStream, &txtOutStreamStateOnEntryStart ) ) { return ELASTIC_APM_STRING_LITERAL_TO_VIEW( ELASTIC_APM_TEXT_OUTPUT_STREAM_NOT_ENOUGH_SPACE_MARKER ); } const char* oldMessageEnd = stringViewEnd( oldMessage ); StringView oldMessageLeft = oldMessage; for ( ;; ) { StringView eolSeq = findEndOfLineSequence( oldMessageLeft ); if ( isEmptyStringView( eolSeq ) ) break; streamStringView( makeStringViewFromBeginEnd( oldMessageLeft.begin, stringViewEnd( eolSeq ) ), &txtOutStream ); if ( sinkSpecificPrefix.length != 0 ) { streamStringView( sinkSpecificPrefix, &txtOutStream ); appendSeparator( &txtOutStream ); } streamStringView( commonPrefix, &txtOutStream ); appendSeparator( &txtOutStream ); oldMessageLeft = makeStringViewFromBeginEnd( stringViewEnd( eolSeq ), oldMessageEnd ); } // If we didn't write anything to new message part then it means the old one is just one line // so there's no need to insert any prefixes if ( isEmptyStringView( textOutputStreamContentAsStringView( &txtOutStream ) ) ) { return ELASTIC_APM_EMPTY_STRING_VIEW; } streamStringView( oldMessageLeft, &txtOutStream ); textOutputStreamEndEntry( &txtOutStreamStateOnEntryStart, &txtOutStream ); return textOutputStreamContentAsStringView( &txtOutStream ); } String concatPrefixAndMsg( Logger* logger , StringView sinkSpecificPrefix , StringView sinkSpecificEndOfLine , StringView commonPrefix , bool prefixNewLines , String msgFmt , va_list msgArgs ) { ELASTIC_APM_ASSERT_VALID_PTR( logger ); ELASTIC_APM_ASSERT_VALID_PTR( logger->messageBuffer ); TextOutputStream txtOutStream = makeTextOutputStream( logger->messageBuffer, loggerMessageBufferSize ); TextOutputStreamState txtOutStreamStateOnEntryStart; if ( ! textOutputStreamStartEntry( &txtOutStream, &txtOutStreamStateOnEntryStart ) ) { return ELASTIC_APM_TEXT_OUTPUT_STREAM_NOT_ENOUGH_SPACE_MARKER; } if ( sinkSpecificPrefix.length != 0 ) { streamStringView( sinkSpecificPrefix, &txtOutStream ); appendSeparator( &txtOutStream ); } streamStringView( commonPrefix, &txtOutStream ); const char* messagePartBegin = textOutputStreamGetFreeSpaceBegin( &txtOutStream ); streamVPrintf( &txtOutStream, msgFmt, msgArgs ); if ( prefixNewLines ) { StringView messagePart = textOutputStreamViewFrom( &txtOutStream, messagePartBegin ); size_t maxSizeForNewMessage = textOutputStreamGetFreeSpaceSize( &txtOutStream ) + messagePart.length - sinkSpecificEndOfLine.length; StringView newMessagePart = insertPrefixAtEachNewLine( logger, sinkSpecificPrefix, commonPrefix, messagePart, maxSizeForNewMessage ); if ( ! isEmptyStringView( newMessagePart ) ) { textOutputStreamGoBack( &txtOutStream, messagePart.length ); streamStringView( newMessagePart, &txtOutStream ); } } streamStringView( sinkSpecificEndOfLine, &txtOutStream ); return textOutputStreamEndEntry( &txtOutStreamStateOnEntryStart, &txtOutStream ); } void writeToStderr( Logger* logger, LogLevel statementLevel, StringView commonPrefix, String msgFmt, va_list msgArgs ) { String fullText = concatPrefixAndMsg( logger , /* sinkSpecificPrefix: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( ELASTIC_APM_LOG_LINE_PREFIX_TRACER_PART ) , /* sinkSpecificEndOfLine: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( "\n" ) , commonPrefix , /* prefixNewLines: */ true , msgFmt , msgArgs ); fprintf( stderr, "%s", fullText ); fflush( stderr ); } #ifndef PHP_WIN32 // syslog is not supported on Windows ////////////////////////////////////////////////////////////////////////////// // // syslog // int logLevelToSyslog( LogLevel level ) { switch ( level ) { case logLevel_trace: // NOLINT(bugprone-branch-clone) case logLevel_debug: return LOG_DEBUG; case logLevel_info: return LOG_INFO; case logLevel_warning: return LOG_WARNING; case logLevel_error: return LOG_ERR; case logLevel_critical: return LOG_CRIT; default: return LOG_DEBUG; } } void writeToSyslog( Logger* logger, LogLevel level, StringView commonPrefix, String msgFmt, va_list msgArgs ) { String fullText = concatPrefixAndMsg( logger , /* sinkSpecificPrefix: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( ELASTIC_APM_LOG_LINE_PREFIX_TRACER_PART ) , /* sinkSpecificEndOfLine: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( "" ) , commonPrefix , /* prefixNewLines: */ false , msgFmt , msgArgs ); syslog( logLevelToSyslog( level ), "%s", fullText ); } // // syslog // ////////////////////////////////////////////////////////////////////////////// #endif // #ifndef PHP_WIN32 // syslog is not supported on Windows #ifdef PHP_WIN32 static void writeToWinSysDebug( Logger* logger, StringView commonPrefix, String msgFmt, va_list msgArgs ) { char sinkSpecificPrefixBuffer[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE]; String fullText = concatPrefixAndMsg( logger , /* sinkSpecificPrefix: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( ELASTIC_APM_LOG_LINE_PREFIX_TRACER_PART ) , /* sinkSpecificEndOfLine: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( "\n" ) , commonPrefix , /* prefixNewLines: */ true , msgFmt , msgArgs ); writeToWindowsSystemDebugger( fullText ); } #endif static void openAndAppendToFile( Logger* logger, String text ) { size_t textLen = strlen( text ); // OLD TODO: Uncomment: Fix lower level system calls - "open" and "write" to get stronger guarantee //#ifdef PHP_WIN32 FILE* file = fopen( logger->config.file, "a" ); if ( file == NULL ) { logger->fileFailed = true; return; } size_t numberOfElementsWritten = fwrite( text, sizeof( *text ), textLen, file ); if ( numberOfElementsWritten != textLen ) { logger->fileFailed = true; return; } //#else // // Use lower level system calls - "open" and "write" to get stronger guarantee: // // // // O_APPEND // // The file is opened in append mode. Before each write(2), the // // file offset is positioned at the end of the file, as if with // // lseek(2). The modification of the file offset and the write // // operation are performed as a single atomic step. // // // // http://man7.org/linux/man-pages/man2/open.2.html // // // int file = open( logger->config.file, O_WRONLY | O_APPEND | O_CREAT ); // if ( file < 0 ) // { // goto failure; // } // size_t numberOfBytesToWrite = ( (long) sizeof( *text ) ) * textLen; // ssize_t numberOfBytesWritten = write( file, text, numberOfBytesToWrite ); // if ( numberOfBytesWritten != numberOfBytesToWrite ) // { // goto failure; // } //#endif //#ifdef PHP_WIN32 if ( file != NULL ) { fclose( file ); } //#else // if ( file >= 0 ) // { // close( file ); // } //#endif return; } static bool isLogFileInGoodState( Logger* logger ) { return ( ! isNullOrEmtpyString( logger->config.file ) ) && ( ! logger->fileFailed ); } void writeToFile( Logger* logger, StringView commonPrefix, String msgFmt, va_list msgArgs ) { ELASTIC_APM_ASSERT( isLogFileInGoodState( logger ), "" ); String fullText = concatPrefixAndMsg( logger , /* sinkSpecificPrefix: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( "" ) , /* sinkSpecificEndOfLine: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( "\n" ) , commonPrefix , /* prefixNewLines: */ true , msgFmt , msgArgs ); openAndAppendToFile( logger, fullText ); } #ifdef ELASTIC_APM_LOG_CUSTOM_SINK_FUNC // Declare to avoid warnings void ELASTIC_APM_LOG_CUSTOM_SINK_FUNC( String fullText ); static void buildFullTextAndWriteToCustomSink( Logger* logger, StringView commonPrefix, String msgFmt, va_list msgArgs ) { char sinkSpecificPrefixBuffer[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE]; String fullText = concatPrefixAndMsg( logger , /* sinkSpecificPrefix: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( ELASTIC_APM_LOG_LINE_PREFIX_TRACER_PART ) , /* sinkSpecificEndOfLine: */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( "" ) , commonPrefix , /* prefixNewLines: */ false , msgFmt , msgArgs ); ELASTIC_APM_LOG_CUSTOM_SINK_FUNC( fullText ); } #endif // #ifdef ELASTIC_APM_LOG_CUSTOM_SINK_FUNC void logWithLogger( Logger* logger , bool isForced , LogLevel statementLevel , StringView category , StringView filePath , UInt lineNumber , StringView funcName , String msgPrintfFmt , ... ) { va_list msgPrintfFmtArgs; va_start( msgPrintfFmtArgs, msgPrintfFmt ); vLogWithLogger( logger , isForced , statementLevel , category , filePath , lineNumber , funcName , msgPrintfFmt , msgPrintfFmtArgs ); va_end( msgPrintfFmtArgs ); } void vLogWithLoggerImpl( Logger* logger , bool isForced , LogLevel statementLevel , StringView category , StringView filePath , UInt lineNumber , StringView funcName , String msgPrintfFmt , va_list msgPrintfFmtArgs ) { if ( logger->reentrancyDepth + 1 > maxLoggerReentrancyDepth ) return; ++ logger->reentrancyDepth; ELASTIC_APM_ASSERT_GT_UINT64( logger->reentrancyDepth, 0 ); ELASTIC_APM_ASSERT_VALID_PTR( logger ); enum { commonPrefixBufferSize = 200 + ELASTIC_APM_TEXT_OUTPUT_STREAM_RESERVED_SPACE_SIZE, }; char commonPrefixBuffer[commonPrefixBufferSize]; StringView commonPrefix = {nullptr, 0}; if ( isForced || logger->config.levelPerSinkType[ logSink_stderr ] >= statementLevel ) { if (commonPrefix.begin == nullptr) { commonPrefix = buildCommonPrefix( statementLevel, category, filePath, lineNumber, funcName, commonPrefixBuffer, commonPrefixBufferSize ); } // create a separate copy of va_list because functions using it (such as fprintf, etc.) modify it va_list msgPrintfFmtArgsCopy; va_copy( /* dst: */ msgPrintfFmtArgsCopy, /* src: */ msgPrintfFmtArgs ); writeToStderr( logger, statementLevel, commonPrefix, msgPrintfFmt, msgPrintfFmtArgsCopy ); va_end( msgPrintfFmtArgsCopy ); } #ifndef PHP_WIN32 if ( isForced || logger->config.levelPerSinkType[ logSink_syslog ] >= statementLevel ) { if (commonPrefix.begin == nullptr) { commonPrefix = buildCommonPrefix( statementLevel, category, filePath, lineNumber, funcName, commonPrefixBuffer, commonPrefixBufferSize ); } // create a separate copy of va_list because functions using it (such as fprintf, etc.) modify it va_list msgPrintfFmtArgsCopy; va_copy( /* dst: */ msgPrintfFmtArgsCopy, /* src: */ msgPrintfFmtArgs ); writeToSyslog( logger, statementLevel, commonPrefix, msgPrintfFmt, msgPrintfFmtArgsCopy ); va_end( msgPrintfFmtArgsCopy ); } #endif #ifdef PHP_WIN32 if ( isForced || logger->config.levelPerSinkType[ logSink_winSysDebug ] >= statementLevel ) { if (commonPrefix.begin == nullptr) { commonPrefix = buildCommonPrefix( statementLevel, category, filePath, lineNumber, funcName, commonPrefixBuffer, commonPrefixBufferSize ); } // create a separate copy of va_list because functions using it (such as fprintf, etc.) modify it va_list msgPrintfFmtArgsCopy; va_copy( /* dst: */ msgPrintfFmtArgsCopy, /* src: */ msgPrintfFmtArgs ); writeToWinSysDebug( logger, commonPrefix, msgPrintfFmt, msgPrintfFmtArgsCopy ); va_end( msgPrintfFmtArgsCopy ); } #endif if ( ( isForced || logger->config.levelPerSinkType[ logSink_file ] >= statementLevel ) && isLogFileInGoodState( logger ) ) { if (commonPrefix.begin == nullptr) { commonPrefix = buildCommonPrefix( statementLevel, category, filePath, lineNumber, funcName, commonPrefixBuffer, commonPrefixBufferSize ); } // create a separate copy of va_list because functions using it (such as fprintf, etc.) modify it va_list msgPrintfFmtArgsCopy; va_copy( /* dst: */ msgPrintfFmtArgsCopy, /* src: */ msgPrintfFmtArgs ); writeToFile( logger, commonPrefix, msgPrintfFmt, msgPrintfFmtArgsCopy ); va_end( msgPrintfFmtArgsCopy ); } #ifdef ELASTIC_APM_LOG_CUSTOM_SINK_FUNC if (commonPrefix.begin == nullptr) { commonPrefix = buildCommonPrefix( statementLevel, category, filePath, lineNumber, funcName, commonPrefixBuffer, commonPrefixBufferSize ); } va_list msgPrintfFmtArgsCopy; va_copy( /* dst: */ msgPrintfFmtArgsCopy, /* src: */ msgPrintfFmtArgs ); buildFullTextAndWriteToCustomSink( logger, commonPrefix, msgPrintfFmt, msgPrintfFmtArgsCopy ); va_end( msgPrintfFmtArgsCopy ); #endif ELASTIC_APM_ASSERT_GT_UINT64( logger->reentrancyDepth, 0 ); -- logger->reentrancyDepth; } static String g_logMutexDesc = "global logger"; static Mutex* g_logMutex = NULL; static __thread bool g_isInLogContext = false; bool isInLogContext() { return g_isInLogContext; } void vLogWithLogger( Logger* logger , bool isForced , LogLevel statementLevel , StringView category , StringView filePath , UInt lineNumber , StringView funcName , String msgPrintfFmt , va_list msgPrintfFmtArgs ) { if ( g_logMutex == NULL ) { #ifndef PHP_WIN32 ELASTIC_APM_LOG_DIRECT_CRITICAL( "g_logMutex is NULL; filePath: %.*s, lineNumber: %d, funcName: %.*s, msgPrintfFmt: %s" , (int)filePath.length, filePath.begin, lineNumber, (int)funcName.length, funcName.begin, msgPrintfFmt ); #endif return; } if ( g_isInLogContext ) { #ifndef PHP_WIN32 ELASTIC_APM_LOG_DIRECT_CRITICAL( "Trying to re-enter logging; filePath: %.*s, lineNumber: %d, funcName: %.*s, msgPrintfFmt: %s" , (int)filePath.length, filePath.begin, lineNumber, (int)funcName.length, funcName.begin, msgPrintfFmt ); #endif return; } g_isInLogContext = true; bool shouldUnlockMutex = false; // Don't log for logging mutex to avoid spamming the log ResultCode resultCode = lockMutexNoLogging( g_logMutex, &shouldUnlockMutex, __FUNCTION__ ); if ( resultCode != resultSuccess ) { ELASTIC_APM_LOG_DIRECT_CRITICAL( "Failed to lock g_logMutex, resultCode: %s (%d); filePath: %.*s, lineNumber: %d, funcName: %.*s, msgPrintfFmt: %s" , resultCodeToString( resultCode ), resultCode, (int)filePath.length, filePath.begin, lineNumber, (int)funcName.length, funcName.begin, msgPrintfFmt ); goto finally; } vLogWithLoggerImpl( logger , isForced , statementLevel , category , filePath , lineNumber , funcName , msgPrintfFmt , msgPrintfFmtArgs ); finally: // Don't log for logging mutex to avoid spamming the log unlockMutexNoLogging( g_logMutex, &shouldUnlockMutex, __FUNCTION__ ); g_isInLogContext = false; } static LogLevel findMaxLevel( const LogLevel* levelsArray, size_t levelsArraySize, LogLevel minLevel ) { int max = minLevel; ELASTIC_APM_FOR_EACH_INDEX( i, levelsArraySize ) if ( levelsArray[ i ] > max ) max = levelsArray[ i ]; return static_cast<LogLevel>(max); } LogLevel calcMaxEnabledLogLevel( LogLevel levelPerSinkType[numberOfLogSinkTypes] ) { return findMaxLevel( levelPerSinkType, numberOfLogSinkTypes, /* minValue */ logLevel_not_set ); } static void setLoggerConfigToDefaults( LoggerConfig* config ) { ELASTIC_APM_FOR_EACH_INDEX( sinkTypeIndex, numberOfLogSinkTypes )config->levelPerSinkType[ sinkTypeIndex ] = defaultLogLevelPerSinkType[ sinkTypeIndex ]; config->file = NULL; } static LogLevel deriveLevelForSink( LogLevel levelForSink, LogLevel generalLevel, LogLevel defaultLevelForSink ) { if ( levelForSink != logLevel_not_set ) return levelForSink; if ( generalLevel != logLevel_not_set ) return generalLevel; return defaultLevelForSink; } static void deriveLoggerConfig( const LoggerConfig* newConfig, LogLevel generalLevel, LoggerConfig* derivedNewConfig ) { LoggerConfig defaultConfig; setLoggerConfigToDefaults( &defaultConfig ); ELASTIC_APM_FOR_EACH_LOG_SINK_TYPE( logSinkType ) { derivedNewConfig->levelPerSinkType[ logSinkType ] = deriveLevelForSink( newConfig->levelPerSinkType[ logSinkType ], generalLevel, defaultConfig.levelPerSinkType[ logSinkType ] ); } } static bool areEqualLoggerConfigs( const LoggerConfig* config1, const LoggerConfig* config2 ) { ELASTIC_APM_FOR_EACH_LOG_SINK_TYPE( logSinkType )if ( config1->levelPerSinkType[ logSinkType ] != config2->levelPerSinkType[ logSinkType ] ) return false; if ( ! areEqualNullableStrings( config1->file, config2->file ) ) return false; return true; } static void logConfigChangeInLevel( String dbgLevelDesc, LogLevel oldLevel, LogLevel newLevel ) { char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE]; TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf ); if ( oldLevel == newLevel ) ELASTIC_APM_LOG_DEBUG( "%s did not change. Its value is still %s." , dbgLevelDesc , streamLogLevel( oldLevel, &txtOutStream ) ); else ELASTIC_APM_LOG_DEBUG( "%s changed from %s to %s." , dbgLevelDesc , streamLogLevel( oldLevel, &txtOutStream ) , streamLogLevel( newLevel, &txtOutStream ) ); } String logSinkTypeName[numberOfLogSinkTypes] = { [logSink_stderr] = "Stderr", #ifndef PHP_WIN32 [ logSink_syslog ] = "Syslog", #endif #ifdef PHP_WIN32 [logSink_winSysDebug] = "Windows system debugger", #endif [logSink_file] = "File" }; LogLevel defaultLogLevelPerSinkType[numberOfLogSinkTypes] = { [logSink_stderr] = logLevel_critical, #ifndef PHP_WIN32 [ logSink_syslog ] = logLevel_info, #endif #ifdef PHP_WIN32 [logSink_winSysDebug] = logLevel_debug, #endif [logSink_file] = logLevel_info }; static void logConfigChange( const LoggerConfig* oldConfig, LogLevel oldMaxEnabledLevel, const LoggerConfig* newConfig, LogLevel newMaxEnabledLevel ) { char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE]; TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf ); ELASTIC_APM_FOR_EACH_LOG_SINK_TYPE( logSinkType ) { textOutputStreamRewind( &txtOutStream ); logConfigChangeInLevel( streamPrintf( &txtOutStream, "Log level for sink %s", logSinkTypeName[ logSinkType ] ) , oldConfig->levelPerSinkType[ logSinkType ] , newConfig->levelPerSinkType[ logSinkType ] ); } textOutputStreamRewind( &txtOutStream ); logConfigChangeInLevel( "Max enabled log level", oldMaxEnabledLevel, newMaxEnabledLevel ); textOutputStreamRewind( &txtOutStream ); if ( areEqualNullableStrings( oldConfig->file, newConfig->file ) ) ELASTIC_APM_LOG_DEBUG( "Path for file logging sink did not change. Its value is still %s." , streamUserString( newConfig->file, &txtOutStream ) ); else ELASTIC_APM_LOG_DEBUG( "Path for file logging sink changed from %s to %s." , streamUserString( oldConfig->file, &txtOutStream ) , streamUserString( newConfig->file, &txtOutStream ) ); } void destructLoggerConfig( LoggerConfig* loggerConfig ) { ELASTIC_APM_ASSERT_VALID_PTR( loggerConfig ); ELASTIC_APM_PEFREE_STRING_AND_SET_TO_NULL( loggerConfig->file ); } ResultCode reconfigureLogger( Logger* logger, const LoggerConfig* newConfig, LogLevel generalLevel ) { ResultCode resultCode; LoggerConfig derivedNewConfig = *newConfig; String filePathCopy = NULL; LoggerConfig oldConfig; LogLevel oldMaxEnabledLevel; deriveLoggerConfig( newConfig, generalLevel, &derivedNewConfig ); if ( areEqualLoggerConfigs( &logger->config, &derivedNewConfig ) ) { ELASTIC_APM_LOG_DEBUG( "Logger configuration did not change" ); resultCode = resultSuccess; goto finally; } if ( newConfig->file != NULL ) { ELASTIC_APM_PEMALLOC_DUP_STRING_IF_FAILED_GOTO( newConfig->file, /* out */ filePathCopy ); } oldConfig = logger->config; oldMaxEnabledLevel = logger->maxEnabledLevel; logger->config = derivedNewConfig; logger->config.file = filePathCopy; filePathCopy = NULL; logger->maxEnabledLevel = calcMaxEnabledLogLevel( logger->config.levelPerSinkType ); logConfigChange( &oldConfig, oldMaxEnabledLevel, &logger->config, logger->maxEnabledLevel ); # ifndef PHP_WIN32 g_elasticApmDirectLogLevelSyslog = logger->config.levelPerSinkType[ logSink_syslog ]; # endif // #ifndef PHP_WIN32 g_elasticApmDirectLogLevelStderr = logger->config.levelPerSinkType[ logSink_stderr ]; destructLoggerConfig( &oldConfig ); resultCode = resultSuccess; finally: return resultCode; failure: ELASTIC_APM_PEFREE_STRING_AND_SET_TO_NULL( filePathCopy ); goto finally; } ResultCode constructLogger( Logger* logger ) { ELASTIC_APM_ASSERT_VALID_PTR( logger ); ResultCode resultCode; if ( g_logMutex == NULL ) { ELASTIC_APM_CALL_IF_FAILED_GOTO( newMutex( &g_logMutex, g_logMutexDesc ) ); } setLoggerConfigToDefaults( &( logger->config ) ); logger->maxEnabledLevel = calcMaxEnabledLogLevel( logger->config.levelPerSinkType ); logger->messageBuffer = NULL; logger->auxMessageBuffer = NULL; logger->fileFailed = false; ELASTIC_APM_PEMALLOC_STRING_IF_FAILED_GOTO( loggerMessageBufferSize, logger->messageBuffer ); ELASTIC_APM_PEMALLOC_STRING_IF_FAILED_GOTO( loggerMessageBufferSize, logger->auxMessageBuffer ); resultCode = resultSuccess; finally: return resultCode; failure: destructLogger( logger ); goto finally; } void destructLogger( Logger* logger ) { ELASTIC_APM_ASSERT_VALID_PTR( logger ); destructLoggerConfig( &( logger->config ) ); ELASTIC_APM_PEFREE_STRING_SIZE_AND_SET_TO_NULL( loggerMessageBufferSize, logger->auxMessageBuffer ); ELASTIC_APM_PEFREE_STRING_SIZE_AND_SET_TO_NULL( loggerMessageBufferSize, logger->messageBuffer ); if ( g_logMutex != NULL ) { deleteMutex( &g_logMutex ); } } Logger* getGlobalLogger() { return &getGlobalTracer()->logger; } ResultCode resetLoggingStateInForkedChild() { // We SHOULD NOT log before resetting state because logging uses thread synchronization // which might deadlock in forked child ResultCode resultCode; g_isInLogContext = true; if ( g_logMutex != NULL ) { deleteMutex( &g_logMutex ); ELASTIC_APM_CALL_IF_FAILED_GOTO( newMutex( &g_logMutex, g_logMutexDesc ) ); } resultCode = resultSuccess; finally: g_isInLogContext = false; return resultCode; failure: goto finally; }