Skip to content

Commit

Permalink
ref(logging): refactor SentryCrashLog into SentryAsyncSafeLog (#4101)
Browse files Browse the repository at this point in the history
  • Loading branch information
armcknight authored Jul 3, 2024
1 parent c151c14 commit 6164c67
Show file tree
Hide file tree
Showing 58 changed files with 831 additions and 1,427 deletions.
36 changes: 8 additions & 28 deletions Sentry.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

162 changes: 162 additions & 0 deletions Sources/Sentry/SentryAsyncSafeLog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Adapted from: https://github.com/kstenerud/KSCrash
//
// SentryAsyncSafeLog.c
//
// Created by Karl Stenerud on 11-06-25.
//
// Copyright (c) 2011 Karl Stenerud. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall remain in place
// in this source code.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

#include "SentryAsyncSafeLog.h"
#include "SentryCrashDebug.h"
#include "SentryInternalCDefines.h"

#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

// Compiler hints for "if" statements
#define likely_if(x) if (__builtin_expect(x, 1))
#define unlikely_if(x) if (__builtin_expect(x, 0))

/** Write a formatted string to the log.
*
* @param fmt The format string, followed by its arguments.
*/
static void writeFmtToLog(const char *fmt, ...);

/** Write a formatted string to the log using a vararg list.
*
* @param fmt The format string.
*
* @param args The variable arguments.
*/
static void writeFmtArgsToLog(const char *fmt, va_list args);

static inline const char *
lastPathEntry(const char *const path)
{
const char *lastFile = strrchr(path, '/');
return lastFile == 0 ? path : lastFile + 1;
}

static inline void
writeFmtToLog(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
writeFmtArgsToLog(fmt, args);
va_end(args);
}

/** The file descriptor where log entries get written. */
static int g_fd = -1;

#if SENTRY_ASYNC_SAFE_LOG_ALSO_WRITE_TO_CONSOLE
static bool g_isDebugging;
static bool g_checkedIsDebugging;
#endif // SENTRY_ASYNC_SAFE_LOG_ALSO_WRITE_TO_CONSOLE

static void
writeToLog(const char *const str)
{
if (g_fd >= 0) {
int bytesToWrite = (int)strlen(str);
const char *pos = str;
while (bytesToWrite > 0) {
int bytesWritten = (int)write(g_fd, pos, (unsigned)bytesToWrite);
unlikely_if(bytesWritten == -1) { break; }
bytesToWrite -= bytesWritten;
pos += bytesWritten;
}
}
write(STDOUT_FILENO, str, strlen(str));

#if SENTRY_ASYNC_SAFE_LOG_ALSO_WRITE_TO_CONSOLE
// if we're debugging, also write the log statements to the console; we only check once for
// performance reasons; if the debugger is attached or detached while running, it will not
// change console-based logging
if (!g_checkedIsDebugging) {
g_checkedIsDebugging = true;
g_isDebugging = sentrycrashdebug_isBeingTraced();
}
if (g_isDebugging) {
fprintf(stdout, "%s", str);
fflush(stdout);
}
#endif // SENTRY_ASYNC_SAFE_LOG_ALSO_WRITE_TO_CONSOLE
}

static inline void
writeFmtArgsToLog(const char *fmt, va_list args)
{
unlikely_if(fmt == NULL) { writeToLog("(null)"); }
else
{
char buffer[SENTRY_ASYNC_SAFE_LOG_C_BUFFER_SIZE];
vsnprintf(buffer, sizeof(buffer), fmt, args);
writeToLog(buffer);
}
}

static inline void
setLogFD(int fd)
{
if (g_fd >= 0 && g_fd != STDOUT_FILENO && g_fd != STDERR_FILENO && g_fd != STDIN_FILENO) {
close(g_fd);
}
g_fd = fd;
}

int
sentry_asyncLogSetFileName(const char *filename, bool overwrite)
{
static int fd = -1;
if (filename != NULL) {
int openMask = O_WRONLY | O_CREAT;
if (overwrite) {
openMask |= O_TRUNC;
}
fd = open(filename, openMask, 0644);
unlikely_if(fd < 0) { return 1; }
if (filename != g_logFilename) {
strncpy(g_logFilename, filename, sizeof(g_logFilename));
}
}

setLogFD(fd);
return 0;
}

void
sentry_asyncLogC(const char *const level, const char *const file, const int line,
const char *const function, const char *const fmt, ...)
{
writeFmtToLog("%s: %s (%u): %s: ", level, lastPathEntry(file), line, function);
va_list args;
va_start(args, fmt);
writeFmtArgsToLog(fmt, args);
va_end(args);
writeToLog("\n");
}
173 changes: 173 additions & 0 deletions Sources/Sentry/SentryAsyncSafeLog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Adapted from: https://github.com/kstenerud/KSCrash
//
// SentryAsyncSafeLog.h
//
// Created by Karl Stenerud on 11-06-25.
//
// Copyright (c) 2011 Karl Stenerud. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall remain in place
// in this source code.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

#ifndef HDR_SENTRY_ASYNC_SAFE_LOG_H
#define HDR_SENTRY_ASYNC_SAFE_LOG_H

#define SENTRY_ASYNC_SAFE_LOG_C_BUFFER_SIZE 1024

/**
* In addition to writing to file, we can also write to the console. This is not safe to do from
* actual async contexts, but can be helpful while running with the debugger attached in certain
* cases. The logger will never write to the console if there is no debugger attached.
* @warning Never commit a change of this definition to 1, or we compromise async-safety in
* production crash reporting.
*/
#define SENTRY_ASYNC_SAFE_LOG_ALSO_WRITE_TO_CONSOLE 0

#ifdef __cplusplus
extern "C" {
#endif

#include <stdbool.h>

static char g_logFilename[1024];

void sentry_asyncLogC(
const char *level, const char *file, int line, const char *function, const char *fmt, ...);

#define i_SENTRY_ASYNC_SAFE_LOG sentry_asyncLogC

#define SENTRY_ASYNC_SAFE_LOG_LEVEL_NONE 0
#define SENTRY_ASYNC_SAFE_LOG_LEVEL_ERROR 10
#define SENTRY_ASYNC_SAFE_LOG_LEVEL_WARN 20
#define SENTRY_ASYNC_SAFE_LOG_LEVEL_INFO 30
#define SENTRY_ASYNC_SAFE_LOG_LEVEL_DEBUG 40
#define SENTRY_ASYNC_SAFE_LOG_LEVEL_TRACE 50

#define SENTRY_ASYNC_SAFE_LOG_LEVEL SENTRY_ASYNC_SAFE_LOG_LEVEL_ERROR

#define a_SENTRY_ASYNC_SAFE_LOG(LEVEL, FMT, ...) \
i_SENTRY_ASYNC_SAFE_LOG(LEVEL, __FILE__, __LINE__, __PRETTY_FUNCTION__, FMT, ##__VA_ARGS__)

// ============================================================================
#pragma mark - API -
// ============================================================================

/** Set the filename to log to.
*
* @param filename The file to write to (NULL = write to stdout).
* @param overwrite If true, overwrite the log file.
* @return 0 if successful, 1 otherwise.
*/
int sentry_asyncLogSetFileName(const char *filename, bool overwrite);

/** Tests if the logger would print at the specified level.
*
* @param LEVEL The level to test for. One of:
* SENTRY_ASYNC_SAFE_LOG_LEVEL_ERROR,
* SENTRY_ASYNC_SAFE_LOG_LEVEL_WARN,
* SENTRY_ASYNC_SAFE_LOG_LEVEL_INFO,
* SENTRY_ASYNC_SAFE_LOG_LEVEL_DEBUG,
* SENTRY_ASYNC_SAFE_LOG_LEVEL_TRACE,
*
* @return TRUE if the logger would print at the specified level.
*/
#define SENTRY_ASYNC_SAFE_LOG_PRINTS_AT_LEVEL(LEVEL) (SENTRY_ASYNC_SAFE_LOG_LEVEL >= LEVEL)

/** Log an error.
* Normal version prints out full context.
*
* @param FMT The format specifier, followed by its arguments.
*/
#if SENTRY_ASYNC_SAFE_LOG_PRINTS_AT_LEVEL(SENTRY_ASYNC_SAFE_LOG_LEVEL_ERROR)
# define SENTRY_ASYNC_SAFE_LOG_ERROR(FMT, ...) \
a_SENTRY_ASYNC_SAFE_LOG("ERROR", FMT, ##__VA_ARGS__)
#else
# define SENTRY_ASYNC_SAFE_LOG_ERROR(FMT, ...)
#endif

/** Log a warning.
* Normal version prints out full context.
*
* @param FMT The format specifier, followed by its arguments.
*/
#if SENTRY_ASYNC_SAFE_LOG_PRINTS_AT_LEVEL(SENTRY_ASYNC_SAFE_LOG_LEVEL_WARN)
# define SENTRY_ASYNC_SAFE_LOG_WARN(FMT, ...) \
a_SENTRY_ASYNC_SAFE_LOG("WARN ", FMT, ##__VA_ARGS__)
#else
# define SENTRY_ASYNC_SAFE_LOG_WARN(FMT, ...)
#endif

/** Log an info message.
* Normal version prints out full context.
*
* @param FMT The format specifier, followed by its arguments.
*/
#if SENTRY_ASYNC_SAFE_LOG_PRINTS_AT_LEVEL(SENTRY_ASYNC_SAFE_LOG_LEVEL_INFO)
# define SENTRY_ASYNC_SAFE_LOG_INFO(FMT, ...) \
a_SENTRY_ASYNC_SAFE_LOG("INFO ", FMT, ##__VA_ARGS__)
#else
# define SENTRY_ASYNC_SAFE_LOG_INFO(FMT, ...)
#endif

/** Log a debug message.
* Normal version prints out full context.
*
* @param FMT The format specifier, followed by its arguments.
*/
#if SENTRY_ASYNC_SAFE_LOG_PRINTS_AT_LEVEL(SENTRY_ASYNC_SAFE_LOG_LEVEL_DEBUG)
# define SENTRY_ASYNC_SAFE_LOG_DEBUG(FMT, ...) \
a_SENTRY_ASYNC_SAFE_LOG("DEBUG", FMT, ##__VA_ARGS__)
#else
# define SENTRY_ASYNC_SAFE_LOG_DEBUG(FMT, ...)
#endif

/** Log a trace message.
* Normal version prints out full context.
*
* @param FMT The format specifier, followed by its arguments.
*/
#if SENTRY_ASYNC_SAFE_LOG_PRINTS_AT_LEVEL(SENTRY_ASYNC_SAFE_LOG_LEVEL_TRACE)
# define SENTRY_ASYNC_SAFE_LOG_TRACE(FMT, ...) \
a_SENTRY_ASYNC_SAFE_LOG("TRACE", FMT, ##__VA_ARGS__)
#else
# define SENTRY_ASYNC_SAFE_LOG_TRACE(FMT, ...)
#endif

/**
* If @c errno is set to a non-zero value after @c statement finishes executing,
* the error value is logged, and the original return value of @c statement is
* returned.
*/
#define SENTRY_ASYNC_SAFE_LOG_ERRNO_RETURN(statement) \
({ \
errno = 0; \
const auto __log_rv = (statement); \
const int __log_errnum = errno; \
if (__log_errnum != 0) { \
SENTRY_ASYNC_SAFE_LOG_ERROR("%s failed with code: %d, description: %s", #statement, \
__log_errnum, strerror(__log_errnum)); \
} \
__log_rv; \
})

#ifdef __cplusplus
}
#endif

#endif // HDR_SENTRY_ASYNC_SAFE_LOG_H
4 changes: 2 additions & 2 deletions Sources/Sentry/SentryBacktrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#if SENTRY_TARGET_PROFILING_SUPPORTED

# include "SentryAsyncSafeLogging.h"
# include "SentryAsyncSafeLog.h"
# include "SentryCompiler.h"
# include "SentryMachLogging.hpp"
# include "SentryStackBounds.hpp"
Expand Down Expand Up @@ -44,7 +44,7 @@ namespace profiling {
std::size_t depth = 0;
MachineContext machineContext;
if (fillThreadState(targetThread.nativeHandle(), &machineContext) != KERN_SUCCESS) {
SENTRY_LOG_ASYNC_SAFE_ERROR("Failed to fill thread state");
SENTRY_ASYNC_SAFE_LOG_ERROR("Failed to fill thread state");
return 0;
}
if (LIKELY(skip == 0)) {
Expand Down
3 changes: 2 additions & 1 deletion Sources/Sentry/SentryFileManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,6 @@ - (void)createPathsWithOptions:(SentryOptions *)options
self.envelopesPath = [self.sentryPath stringByAppendingPathComponent:EnvelopesPathComponent];
}

#if SENTRY_TARGET_PROFILING_SUPPORTED
/**
* @note This method must be statically accessible because it will be called during app launch,
* before any instance of @c SentryFileManager exists, and so wouldn't be able to access this path
Expand All @@ -752,6 +751,8 @@ - (void)createPathsWithOptions:(SentryOptions *)options
return sentryApplicationSupportPath;
}

#if SENTRY_TARGET_PROFILING_SUPPORTED

NSURL *_Nullable sentryLaunchConfigFileURL = nil;

NSURL *_Nullable launchProfileConfigFileURL(void)
Expand Down
Loading

0 comments on commit 6164c67

Please sign in to comment.