warning( 'Cookies set on {url} with Cache-Control "{cache-control}"', [ 'url' => WebRequest::getGlobalRequestURL(), 'set-cookie' => self::sanitizeSetCookie( $headers['set-cookie'] ), 'cache-control' => $cacheControl ?: '', ] ); } } $telemetryHeaders = Telemetry::getInstance()->getRequestHeaders(); // Set the request ID/trace prams on the response, so edge infrastructure can log it. // FIXME this is not an ideal place to do it, but the most reliable for now. foreach ( $telemetryHeaders as $header => $value ) { if ( !isset( $headers[strtolower( $header )] ) ) { header( "$header: $value" ); } } // Save a backtrace for logging in case it turns out that headers were sent prematurely self::$headersSentException = new RuntimeException( 'Headers already sent from this point' ); } /** * Log a warning message if headers have already been sent. This can be * called before flushing the output. * * @since 1.29 */ public static function warnIfHeadersSent() { if ( !self::$messageSent && headers_sent( $filename, $line ) ) { self::$messageSent = true; MWDebug::warning( 'Headers already sent, should send headers earlier than ' . wfGetCaller( 3 ) ); $logger = LoggerFactory::getInstance( 'headers-sent' ); $logger->error( 'Warning: headers were already sent (output started at ' . $filename . ':' . $line . ')', [ 'exception' => self::$headersSentException, 'detection-trace' => new RuntimeException( 'Detected here' ), ] ); } } /** * Sanitize Set-Cookie headers for logging. * @param array $values List of header values. * @return string */ public static function sanitizeSetCookie( array $values ) { $sanitizedValues = []; foreach ( $values as $value ) { // Set-Cookie header format: =; $parts = explode( ';', $value ); [ $name, $value ] = explode( '=', $parts[0], 2 ); if ( strlen( $value ) > 8 ) { $value = substr( $value, 0, 8 ) . '...'; $parts[0] = "$name=$value"; } $sanitizedValues[] = implode( ';', $parts ); } return implode( "\n", $sanitizedValues ); } }