Files
comaps/iphone/CoreApi/CoreApi/Logger/Logger.mm
Konstantin Pastbin e3e4a1985a Organic Maps sources as of 02.04.2025 (fad26bbf22ac3da75e01e62aa01e5c8e11861005)
To expand with full Organic Maps and Maps.ME commits history run:
  git remote add om-historic [om-historic.git repo url]
  git fetch --tags om-historic
  git replace squashed-history historic-commits
2025-05-08 21:10:51 +07:00

258 lines
8.3 KiB
Plaintext

#import "Logger.h"
#import <OSLog/OSLog.h>
#include "base/logging.hpp"
#include "coding/zip_creator.hpp"
@interface Logger ()
@property (nullable, nonatomic) NSFileHandle * fileHandle;
@property (nonnull, nonatomic) os_log_t osLogger;
/// This property is introduced to avoid the CoreApi => Maps target dependency and stores the MWMSettings.isFileLoggingEnabled value.
@property (class, nonatomic) BOOL fileLoggingEnabled;
@property (class, readonly, nonatomic) dispatch_queue_t fileLoggingQueue;
+ (Logger *)logger;
+ (void)enableFileLogging;
+ (void)disableFileLogging;
+ (void)logMessageWithLevel:(base::LogLevel)level src:(base::SrcPoint const &)src message:(std::string const &)message;
+ (void)tryWriteToFile:(std::string const &)logString;
+ (NSURL *)getZippedLogFile:(NSString *)logFilePath;
+ (void)removeFileAtPath:(NSString *)filePath;
+ (base::LogLevel)baseLevel:(LogLevel)level;
@end
// Subsystem and category are used for the OSLog.
NSString * const kLoggerSubsystem = [[NSBundle mainBundle] bundleIdentifier];
NSString * const kLoggerCategory = @"OM";
NSString * const kLogFileName = @"log.txt";
NSString * const kZipLogFileExtension = @"zip";
NSString * const kLogFilePath = [[NSFileManager.defaultManager temporaryDirectory] URLByAppendingPathComponent:kLogFileName].path;
// TODO: (KK) Review and change this limit after some testing.
NSUInteger const kMaxLogFileSize = 1024 * 1024 * 100; // 100 MB;
@implementation Logger
static BOOL _fileLoggingEnabled = NO;
+ (void)initialize
{
if (self == [Logger class]) {
SetLogMessageFn(&LogMessage);
SetAssertFunction(&AssertMessage);
}
}
+ (Logger *)logger {
static Logger * logger = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
logger = [[self alloc] init];
});
return logger;
}
+ (dispatch_queue_t)fileLoggingQueue {
static dispatch_queue_t fileLoggingQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
fileLoggingQueue = dispatch_queue_create("app.organicmaps.fileLoggingQueue", attributes);
});
return fileLoggingQueue;
}
- (instancetype)init {
self = [super init];
if (self) {
_osLogger = os_log_create(kLoggerSubsystem.UTF8String, kLoggerCategory.UTF8String);
}
return self;
}
// MARK: - Public
+ (void)setFileLoggingEnabled:(BOOL)fileLoggingEnabled {
fileLoggingEnabled ? [self enableFileLogging] : [self disableFileLogging];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
LOG_SHORT(LINFO, ("Local time:", NSDate.date.description.UTF8String, ", Time Zone:", NSTimeZone.defaultTimeZone.abbreviation.UTF8String));
});
LOG(LINFO, ("File logging is enabled:", _fileLoggingEnabled ? "YES" : "NO"));
}
+ (BOOL)fileLoggingEnabled {
return _fileLoggingEnabled;
}
+ (void)log:(LogLevel)level message:(NSString *)message {
LOG_SHORT([self baseLevel:level], (message.UTF8String));
}
+ (BOOL)canLog:(LogLevel)level {
return [Logger baseLevel:level] >= base::g_LogLevel;
}
+ (nullable NSURL *)getLogFileURL {
if ([self fileLoggingEnabled]) {
if (![NSFileManager.defaultManager fileExistsAtPath:kLogFilePath]) {
LOG(LERROR, ("Log file doesn't exist while file logging is enabled:", kLogFilePath.UTF8String));
return nil;
}
return [self getZippedLogFile:kLogFilePath];
} else {
// Fetch logs from the OSLog store.
if (@available(iOS 15.0, *)) {
NSError * error;
OSLogStore * store = [OSLogStore storeWithScope:OSLogStoreCurrentProcessIdentifier error:&error];
if (error) {
LOG(LERROR, (error.localizedDescription.UTF8String));
return nil;
}
NSPredicate * predicate = [NSPredicate predicateWithFormat:@"subsystem == %@", kLoggerSubsystem];
OSLogEnumerator * enumerator = [store entriesEnumeratorWithOptions:{} position:nil predicate:predicate error:&error];
if (error) {
LOG(LERROR, (error.localizedDescription.UTF8String));
return nil;
}
NSMutableString * logString = [NSMutableString string];
NSString * kNewLineStr = @"\n";
id object;
while (object = [enumerator nextObject]) {
if ([object isMemberOfClass:[OSLogEntryLog class]]) {
[logString appendString:[object composedMessage]];
[logString appendString:kNewLineStr];
}
}
if (logString.length == 0) {
LOG(LINFO, ("OSLog entry is empty."));
return nil;
}
[NSFileManager.defaultManager createFileAtPath:kLogFilePath contents:[logString dataUsingEncoding:NSUTF8StringEncoding] attributes:nil];
return [self getZippedLogFile:kLogFilePath];
} else {
return nil;
}
}
}
+ (uint64_t)getLogFileSize {
Logger * logger = [self logger];
return logger.fileHandle != nil ? [logger.fileHandle offsetInFile] : 0;
}
// MARK: - C++ injection
void LogMessage(base::LogLevel level, base::SrcPoint const & src, std::string const & message)
{
[Logger logMessageWithLevel:level src:src message:message];
CHECK_LESS(level, base::g_LogAbortLevel, ("Abort. Log level is too serious", level));
}
bool AssertMessage(base::SrcPoint const & src, std::string const & message)
{
[Logger logMessageWithLevel:base::LCRITICAL src:src message:message];
return true;
}
// MARK: - Private
+ (void)enableFileLogging {
Logger * logger = [self logger];
NSFileManager * fileManager = [NSFileManager defaultManager];
// Create a log file if it doesn't exist and setup file handle for writing.
if (![fileManager fileExistsAtPath:kLogFilePath])
[fileManager createFileAtPath:kLogFilePath contents:nil attributes:nil];
NSFileHandle * fileHandle = [NSFileHandle fileHandleForWritingAtPath:kLogFilePath];
if (fileHandle == nil) {
LOG(LERROR, ("Failed to open log file for writing", kLogFilePath.UTF8String));
[self disableFileLogging];
return;
}
// Clean up the file if it exceeds the maximum size.
if ([fileManager contentsAtPath:kLogFilePath].length > kMaxLogFileSize)
[fileHandle truncateFileAtOffset:0];
logger.fileHandle = fileHandle;
_fileLoggingEnabled = YES;
}
+ (void)disableFileLogging {
Logger * logger = [self logger];
[logger.fileHandle closeFile];
logger.fileHandle = nil;
[self removeFileAtPath:kLogFilePath];
_fileLoggingEnabled = NO;
}
+ (void)logMessageWithLevel:(base::LogLevel)level src:(base::SrcPoint const &)src message:(std::string const &)message {
// Build the log message string.
auto & logHelper = base::LogHelper::Instance();
std::ostringstream output;
// TODO: (KK) Either guard this call, or refactor thread ids in logHelper.
logHelper.WriteProlog(output, level);
logHelper.WriteLog(output, src, message);
auto const logString = output.str();
// Log the message into the system log.
os_log([self logger].osLogger, "%{public}s", logString.c_str());
if (level < base::GetDefaultLogAbortLevel())
dispatch_async([self fileLoggingQueue], ^{ [self tryWriteToFile:logString]; });
else
[self tryWriteToFile:logString];
}
+ (void)tryWriteToFile:(std::string const &)logString {
NSFileHandle * fileHandle = [self logger].fileHandle;
if (fileHandle != nil) {
[fileHandle seekToEndOfFile];
[fileHandle writeData:[NSData dataWithBytes:logString.c_str() length:logString.length()]];
}
}
+ (NSURL *)getZippedLogFile:(NSString *)logFilePath {
NSString * zipFilePath = [[logFilePath stringByDeletingPathExtension] stringByAppendingPathExtension:kZipLogFileExtension];
auto const success = CreateZipFromFiles({logFilePath.UTF8String}, zipFilePath.UTF8String);
if (!success) {
LOG(LERROR, ("Failed to zip log file:", kLogFilePath.UTF8String, ". The original file will be returned."));
return [NSURL fileURLWithPath:logFilePath];
}
[self removeFileAtPath:kLogFilePath];
return [NSURL fileURLWithPath:zipFilePath];
}
+ (void)removeFileAtPath:(NSString *)filePath {
if ([NSFileManager.defaultManager fileExistsAtPath:filePath]) {
NSError * error;
[NSFileManager.defaultManager removeItemAtPath:filePath error:&error];
if (error)
LOG(LERROR, (error.localizedDescription.UTF8String));
}
}
+ (base::LogLevel)baseLevel:(LogLevel)level {
switch (level) {
case LogLevelDebug: return LDEBUG;
case LogLevelInfo: return LINFO;
case LogLevelWarning: return LWARNING;
case LogLevelError: return LERROR;
case LogLevelCritical: return LCRITICAL;
}
}
@end