【iOS】CocoaLumberJack日志庫集成

前言

CocoaLumberjack是適用于Mac和iOS的快速,簡單,功能強大且靈活的日志記錄框架。

最近想要升級項目中的Log庫,比較了一番之后,發現CocoaLumberjack這個庫可自定義性特別強,能夠滿足我們的所有要求,而且使用人數很多,可維護性強,性能也不錯。于是最終選擇了這個庫進行集成。集成這個庫的過程也花了一段時間,下面會用一步一步介紹這個庫的使用。最后會將這些功能集成到一個文件里面,方便使用。(話說好想吐槽下這個庫的名字。。。)
本文Demo

基本使用

使用cocoapod安裝庫:pod 'CocoaLumberjack','~> 3.6.1'
由于Log庫在所有的文件中都可能用到,故最好是在項目中的全局頭文件PCH文件中導入Log庫的頭文件。另外,這個庫有個挺特別的地方,需要我們自己定義一個ddLogLevel變量,用來定義當前日志的輸出等級。

//項目的pch文件中
#import <CocoaLumberjack/CocoaLumberjack.h>
//定義Log等級
static const DDLogLevel ddLogLevel = DDLogLevelDebug;

如上所示,上面定義了ddLogLevel等級為DDLogLevelDebug。

然后我們在項目入口進行初始化,就可以正常使用日志庫了。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //添加控制臺logger
    [DDLog addLogger:[DDOSLogger sharedInstance]];
    
    //打印,跟NSLog用法一致
    DDLogError(@"DDLogError");
    DDLogWarn(@"DDLogWarn");
    DDLogInfo(@"My name is = %@",@"謙言忘語");
    DDLogDebug(@"I am %d years old",18);
    DDLogVerbose(@"DDLogVerbose");
    
    return YES;
}
控制臺顯示結果

只需要將我們熟悉的NSLog替換為CocoaLumberjack提供的DDLogInfo等就可以了。所以使用起來還是挺簡單的,符合我們以往的使用習慣。

跟NSLog不一樣的地方是,CocoaLumberjack提供了不同等級的Log輸出,一共有5種等級,以下為5種等級的宏定義。

DDLogError(frmt, ...)      //錯誤      當ddLogLevel = DDLogLevelError及以上等級時會輸出
DDLogWarn(frmt, ...)       //警告      當ddLogLevel = DDLogLevelWarning及以上等級時會輸出
D
DDLogInfo(frmt, ...)       //信息      當ddLogLevel = DDLogLevelInfo及以上等級時會輸出
D
DDLogDebug(frmt, ...)      //調試      當ddLogLevel = DDLogLevelDebug及以上等級時會輸出
D
DDLogVerbose(frmt, ...)    //詳細      當ddLogLevel = DDLogLevelVerbose及以上等級時會輸出

輸出的等級由我們剛開始定義的ddLogLevel決定。比如我們上面定義了ddLogLevel = DDLogLevelDebug,那么debug及以下等級的Log都能夠輸出,而使用DDLogVerbose的Log就不會輸出。

Logger

CocoaLumberjack內置了以下幾種Logger。

DDFileLogger:此類提供了一個記錄器,可將日志語句寫入文件。
DDOSLogger:此類提供了Apple os_log工具的記錄器。
DDASLLogger:此類為Apple系統日志工具提供了一個記錄器。
DDTTYLogger:此類為終端輸出或Xcode控制臺輸出提供了記錄器。

有必要解釋以下幾種Logger的區別。
DDFileLogger:很容易理解,是將log寫入到文件中。
DDOSLogger:在iOS10開始使用,在將Log輸出到 控制臺.app 和 Xcode控制臺。跟NSLog的輸出方式一致。當然,經過處理之后,性能會比直接使用NSLog要好。
DDASLLogger:將日志寫入到控制臺.app中。在iOS10開始過時
DDTTYLogger:將日志寫入到Xcode控制臺。

而我們常用給的NSLog會將日志寫入到控制臺.app和Xcode控制臺。
所以,想要替換NSLog,官方推薦的做法是:
在iOS10及以上系統版本,使用DDOSLogger。
在iOS10以下版本,使用DDASLLogger+DDTTYLogger。

另外,如果你在系統 控制臺.app 里面查看log的時候,看不到對應的log,那很可能是因為你沒有把 控制臺.app 的【包括簡介信息]【包括調試信息】勾選上。

DDOSLogger需要在控制臺勾選上簡介信息和調試信息

日志等級修改

由于項目需要,我們需要動態修改日志的等級。
就是說我們需要在APP運行過程中修改日志等級。比如啟動的時候,默認等級為DDLogLevelWarning。當我們需要查看更詳細日志的時候,就將日志等級改為DDLogLevelVerbose
怎么做呢?
之前做的時候在網上搜索了一圈,發現相關的資料并不算多,有些做到了,但是搞得很復雜。
其實這個功能實現起來不需要那么復雜。CocoaLumberjack是通過上面我們定義的ddLogLevel這個變量來控制當前輸出的日志等級的。只要我們在APP運行的某個時刻修改ddLogLevel的值就可以了。
所以我們需要對ddLogLevel修改下,去掉const修飾

//定義Log等級,默認為DDLogLevelWarning
static DDLogLevel ddLogLevel = DDLogLevelDebug;

然后適當的地方修改ddLogLevel的值

//修改Log等級為DDLogLevelVerbose
ddLogLevel = DDLogLevelVerbose

這樣就可以實現動態修改等級的需求了。
但是這種方案不能實現我們的要求,因為這種方案只能修改某一個文件的內的log等級,就是說我們AppDelegate.m里面修改了log等級為DDLogLevelVerbose,那么就只有在這個類里面生效,在ViewController.m是不生效的。
于是采用了另外一種方案。

更好的日志等級動態修改方案

首先我們新建一個類(名字可以為SQLogManager),在SQLogManager.h文件中聲明一個ddLogLevel外部變量,再然后在SQLogManager.m文件里面定義ddLogLevel的初始化值。

//定義外部變量
extern DDLogLevel ddLogLevel;

@interface SQLogManager : NSObject

@end
#import "SQLogManager.h"

DDLogLevel ddLogLevel = DDLogLevelDebug;

@implementation SQLogManager
@end

然后pch文件導入該頭文件

#import <CocoaLumberjack/CocoaLumberjack.h>
#import "SQLogManager.h"

這樣在外面任何的地方修改了ddLogLevel的值,就能夠全局生效了。

修改輸出格式

我們平常用的NSLog的輸出格式是這樣的

2020-05-03 16:31:07.418620+0800 CocoaLumberjackDemo[2943:118557] DDLogVerbose

DDOSLogger的輸出默認是這樣子的

2020-05-03 16:31:07.420348+0800 CocoaLumberjackDemo[2943:118557] DDOSLogger格式test

可以看到兩個是基本一樣的。
但是,很多時候我們需要修改這個東西,比如我的需求就是在我們的打印前面統一加上前綴[SQ],這樣在系統的 控制臺.app 里面查看log的時候一搜索就能夠輕易定位到我們APP自己的輸出,而不是系統或者其他的輸出,排除干擾(話說系統的日志控制臺的log真多,坑)。當然,更多的人想要在前面輸出當前的Log所在的類名-方法名-行數等等,便于定位問題。
CocoaLumberjack提供了很好的修改輸出格式的方式。

  • 首先,我們需要新建一個繼承NSObject的類(比如命名為SQLogFormatter)
  • 然后遵循DDLogFormatter協議
  • 實現DDLogFormatter協議里面的- (NSString *)formatLogMessage:(DDLogMessage *)logMessage方法
    在方法里面就可以定義你所想要的格式。例如:
@implementation SQLogFormatter

 - (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
     NSString *logLevel = nil;
     switch (logMessage->_flag) {
         case DDLogFlagError:
             logLevel = @"Error";
             break;
         case DDLogFlagWarning:
             logLevel = @"Warn";
             break;
         case DDLogFlagInfo:
             logLevel = @"Info";
             break;
         case DDLogFlagDebug:
             logLevel = @"Debug";
             break;
         default:
             logLevel = @"Verbose";
             break;
     }
     NSString *formatLog = [NSString stringWithFormat:@"[SQ]%@%@-%ld %@",logLevel,logMessage->_function,logMessage->_line,logMessage->_message];
     return formatLog;
 }

@end

DDLogMessage這個對象里面有很多可以用到的成員變量,這些都是可以利用的。

@interface DDLogMessage : NSObject <NSCopying>
{
    // Direct accessors to be used only for performance
    @public
    NSString *_message;
    DDLogLevel _level;
    DDLogFlag _flag;
    NSInteger _context;
    NSString *_file;
    NSString *_fileName;
    NSString *_function;
    NSUInteger _line;
    id _tag;
    DDLogMessageOptions _options;
    NSDate * _timestamp;
    NSString *_threadID;
    NSString *_threadName;
    NSString *_queueLabel;
    NSUInteger _qos;
}
  • 接著我們需要設置DDOSLoggerlogFormatter屬性
    將我們剛才創建的SQLogFormatter設置進去,就可以修改輸出格式了。
    //添加控制臺logger
    DDOSLogger *osLogger = [DDOSLogger sharedInstance];
    osLogger.logFormatter = [[SQLogFormatter alloc] init];
    [DDLog addLogger:osLogger];

來個對比結果如下:

2020-05-03 17:21:19.567059+0800 CocoaLumberjackDemo[3453:137167] NSLog格式test
2020-05-03 17:21:19.570009+0800 CocoaLumberjackDemo[3453:137215] [SQ]Info-[AppDelegate application:didFinishLaunchingWithOptions:]-49 DDOSLogger格式test

值得一說的是,每次打印日志之前,都會訪問這個方法,所以不要在這個方法里面做太多的邏輯,不然會影響性能。

log寫入到文件

之前介紹過,DDFileLogger可以將日志寫入到文件。所以,添加一個DDFileLogger就可以將日志記錄到文件里面了。
我們來個簡單的,創建一個DDFileLogger對象,然后添加到DDLog

    //添加文件寫入logger
    DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
    [DDLog addLogger:fileLogger];

默認的log日志在沙盒的Library/Caches/Logs目錄中,是一個以.log為后綴的文本文件。如下圖所示,下面那個就是文本文件的內容

log文件以及內容

log文件的屬性自定義

根據項目需求,我們需要對log文件做很多的自定義設置。
默認情況下,log文件在多次啟動的時候是會重用的,24小時內將log寫入到同一個文件中,當文件大小超過1MB或者創建時間超過24小時,會新生成一個log文件,后面的log會寫入到新的文件中。文件的個數超過5個的時候,會刪除舊的文件。logs文件夾大小超過20M的時候,也會刪除舊的文件以釋放磁盤空間。
CocoaLumberjack本身提供了這些屬性讓我們自定義。直接代碼解釋:

    DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
    //重用log文件,不要每次啟動都創建新的log文件(默認值是NO)
    fileLogger.doNotReuseLogFiles = NO;
    //log文件在24小時內有效,超過時間創建新log文件(默認值是24小時)
    fileLogger.rollingFrequency = 60*60*24;
    //log文件的最大3M(默認值1M)
    fileLogger.maximumFileSize = 1024*1024*3;
    //最多保存7個log文件(默認值是5)
    fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
    //log文件夾最多保存10M(默認值是20M)
    fileLogger.logFileManager.logFilesDiskQuota = 1014*1024*20;
    
    //添加文件寫入logger
    [DDLog addLogger:fileLogger];

Log文件夾與文件名的獲取

有些時候我們想要獲取到log文件所在的路徑,用來進行一些操作。比如將log文件讀取出來查看,或者將log文件上傳到服務器去。這些路徑可以從fileLogger對象里面能夠獲取到。

    //logs文件夾路徑
    DDLogInfo(@"logsDirectory=%@",fileLogger.logFileManager.logsDirectory);
    //logs文件夾的所有log文件路徑
    DDLogInfo(@"sortedLogFilePaths=%@",fileLogger.logFileManager.sortedLogFilePaths);
    //當前活躍的log文件路徑
    DDLogInfo(@"currentFilePath=%@",fileLogger.currentLogFileInfo.filePath);

log文件夾和文件名的自定義

我們項目中需要將log文件夾的內容上傳到服務器。默認的Log文件存放在沙盒的Library/Caches/Logs目錄中,我想要在Logs文件夾之前加一個文件夾,便于存放上傳的相關設置。也就是說想要把log文件的目錄改為沙盒的Library/Caches/SQLog/Logs目錄中。
這個在創建DDFileogger的時候可以進行設置。

    //修改Logs文件夾的位置
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *baseDir = paths.firstObject;
    NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"SQLog/Logs"];
    DDLogFileManagerDefault *defaultManager = [[DDLogFileManagerDefault alloc] initWithLogsDirectory:logsDirectory];
    
    DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:defaultManager];

還有個問題,我想要修改log文件名字,默認的log名字是 <bundle identifier> <date> <time>.log,例如com.organization.myapp 2013-12-03 17-14.log。中間會有個空格,我擔心在處理的過程中空格可能會出什么不可預知的問題,所以不想要這個空格(就看這個空格不爽,哈哈)。該如何修改這個log的名字呢。
這個就沒有那么容易了,需要

  • 1.新建一個繼承于DDLogFileManagerDefault的類(姑且命名為SQFileManager),
  • 2.然后重寫- (NSString *)newLogFileName方法和- (BOOL)isLogFile:(NSString *)fileName方法。
#import "SQLogFileManager.h"

@implementation SQLogFileManager

//重寫方法(log文件名生成規則)
- (NSString *)newLogFileName {
    
    NSString *timeStamp = [self getTimestamp];
    
    return [NSString stringWithFormat:@"%@.log", timeStamp];
}

//重寫方法(是否是log文件)
- (BOOL)isLogFile:(NSString *)fileName {
    
    BOOL hasProperSuffix = [fileName hasSuffix:@".log"];
    
    return hasProperSuffix;
}

- (NSString *)getTimestamp {
    static dispatch_once_t onceToken;
    static NSDateFormatter *dateFormatter;
    dispatch_once(&onceToken, ^{
        dateFormatter = [NSDateFormatter new];
        [dateFormatter setDateFormat:@"YYYY.MM.dd-HH.mm.ss"];
    });
    return [dateFormatter stringFromDate:NSDate.date];
}

@end

另外,在這個SQLogFileManager類里面,我們還可以重寫- (NSString *)defaultLogsDirectory方法,來修改Log文件夾路徑

//重寫方法(log文件夾路徑)
- (NSString *)defaultLogsDirectory {

#if TARGET_OS_IPHONE
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *baseDir = paths.firstObject;
    NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"SQLog/Logs"];
#else
    NSString *appName = [[NSProcessInfo processInfo] processName];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
    NSString *basePath = ([paths count] > 0) ? paths[0] : NSTemporaryDirectory();
    NSString *logsDirectory = [[basePath stringByAppendingPathComponent:@"SQLog/Logs"] stringByAppendingPathComponent:appName];
#endif

    return logsDirectory;
}
  • 3.在創建fileLogger對象的時候設置我們自定義的logFileManager
    這樣就可以修改文件夾目錄位置和log文件名字了。
    //使用自定義的logFileManager
    SQLogFileManager *fileManager = [[SQLogFileManager alloc] init];
    DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:fileManager];

自定義log文件的log輸出格式

跟之前的DDOSLogger自定義輸出格式一樣,我們想要自定義log文件的輸出格式。我們可以用之前的SQLogFormatter類,但是,這個格式里面并沒有時間戳。。。尷尬了,所以還是得自定義一個。方法跟之前一樣,創建一個類,遵循DDLogFormatter協議,然后實現- (NSString *)formatLogMessage:(DDLogMessage *)logMessage方法

#import "SQFileLogformatter.h"

@implementation SQFileLogformatter

- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
    NSString *logLevel = nil;
    switch (logMessage->_flag) {
        case DDLogFlagError:
            logLevel = @"Error";
            break;
        case DDLogFlagWarning:
            logLevel = @"Warn";
            break;
        case DDLogFlagInfo:
            logLevel = @"Info";
            break;
        case DDLogFlagDebug:
            logLevel = @"Debug";
            break;
        default:
            logLevel = @"Verbose";
            break;
    }
    NSString *formatLog = [NSString stringWithFormat:@"%@[SQ]%@%@-%ld %@",logMessage->_timestamp, logLevel,logMessage->_function,logMessage->_line,logMessage->_message];
    return formatLog;
}
- (NSString *)getTimeStringWithDate:(NSDate *)date {
    static NSDateFormatter *dateFormatter;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dateFormatter = [NSDateFormatter new];
        [dateFormatter setDateFormat:@"YYYY.MM.dd-HH.mm.ss.SSS"];
    });
    return [dateFormatter stringFromDate:date];
}

@end

然后設置fileLogger的formatter就可以了。

    DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:fileManager];
    fileLogger.logFormatter = [[SQFileLogformatter alloc] init];

清空日志

項目有個需求,就是測試期間需要清空文件里面的日志,便于查看后面某個操作的日志。這個功能也可以實現,只是實現起來會跟想象中不太一樣。清空是一個破壞性的操作,刪除一個東西之后想要找回來就不容易了。所以沒有找到CocoaLumberjack有類似的接口提供。但是靈活一點,我們不就是不想看到前面的日志嗎?直接生成一個新的日志文件不就可以了?清清白白、干干凈凈,完美。

    //生成一個新的日志文件,之后的日志寫入到新的文件
    [_fileLogger rollLogFileWithCompletionBlock:^{
        DDLogInfo(@"rollLogFileWithCompletionBlock");
    }];

關閉log顯示

在APP運行過程中,我們想要關閉log。
有兩種方案:一種方案是將ddLogLevel的值改為DDLogLevelOff。這樣對所有的Logger都會生效,不會有輸出。
另外一種方案就是刪除Logger。

    //刪除某個Logger
    [DDLog removeLogger:osLogger];
    //刪除所有Logger
    //[DDLog removeAllLoggers];

整合

經過上面的積累,我們就可以將這些功能都整合到一個管理類里面進行處理了。

#import <Foundation/Foundation.h>
#import <CocoaLumberjack/CocoaLumberjack.h>

//聲明外部變量
extern DDLogLevel ddLogLevel;

// 默認的宏,方便使用
#define Log(frmt, ...)        LogI(frmt, ##__VA_ARGS__)

// 提供不同的宏,對應到特定參數的對外接口
#define LogE(frmt, ...)       DDLogError(frmt, ##__VA_ARGS__)
#define LogW(frmt, ...)       DDLogWarn(frmt, ##__VA_ARGS__)
#define LogI(frmt, ...)       DDLogInfo(frmt, ##__VA_ARGS__)
#define LogD(frmt, ...)       DDLogDebug(frmt, ##__VA_ARGS__)
#define LogV(frmt, ...)       DDLogVerbose(frmt, ##__VA_ARGS__)


@interface SQLogManager : NSObject

//初始化
+ (void)start;
+ (instancetype)sharedInstance;
- (instancetype)init NS_UNAVAILABLE;

//添加控制臺logger
- (void)addOSLogger;
//添加文件寫入Logger
- (void)addFileLogger;
//移除控制臺logger
- (void)removeOSLogger;
//移除文件寫入Logger
- (void)removeFileLogger;

#pragma mark - 等級切換

//切換log等級
- (void)switchLogLevel:(DDLogLevel)logLevel;

#pragma mark - 文件Log操作

- (void)createAndRollToNewFile;
//所有log文件路徑
- (NSArray *)filePaths;
//當前活躍的log文件路徑
- (NSString *)currentFilePath;

@end
#import "SQLogManager.h"
#import "SQLogFormatter.h"
#import "SQFileLogFormatter.h"
#import "SQLogFileManager.h"

//設置默認的log等級
#ifdef DEBUG
DDLogLevel ddLogLevel = DDLogLevelDebug;
#else
DDLogLevel ddLogLevel = DDLogLevelWarning;
#endif


@interface SQLogManager ()

@property (nonatomic, strong)DDFileLogger *fileLogger;//控制臺logger
@property (nonatomic, strong)DDOSLogger *osLogger;//文件寫入Logger

@end

@implementation SQLogManager

+ (void)start {
    
    [self sharedInstance];
}

+ (instancetype)sharedInstance {
    static id sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}




#pragma mark - Logger添加及刪除

//添加控制臺logger
- (void)addOSLogger {
    [DDLog addLogger:self.osLogger];
}
//添加文件寫入Logger
- (void)addFileLogger {
    [DDLog addLogger:self.fileLogger];
}
//移除控制臺logger
- (void)removeOSLogger {
    [DDLog removeLogger:_osLogger];
}
//移除文件寫入Logger
- (void)removeFileLogger {
    [DDLog removeLogger:_fileLogger];
}


#pragma mark - 等級切換

//切換log等級
- (void)switchLogLevel:(DDLogLevel)logLevel {
        
    ddLogLevel = logLevel;
}


#pragma mark - 文件Log操作

- (void)createAndRollToNewFile {
    //achive現在的文件,新生成一個文件,并將以后的log寫入新文件
    [_fileLogger rollLogFileWithCompletionBlock:^{
        NSLog(@"rollLogFileWithCompletionBlock");
    }];
}

//所有log文件路徑
- (NSArray *)filePaths {
    return _fileLogger.logFileManager.sortedLogFilePaths;
}

//當前活躍的log文件路徑
- (NSString *)currentFilePath {
    
    NSString *filePath = _fileLogger.currentLogFileInfo.filePath;
    return filePath;
}


#pragma mark - 懶加載

//控制臺logger
- (DDOSLogger *)osLogger {
    if (!_osLogger) {
        
        _osLogger = [DDOSLogger sharedInstance];
        //自定義輸出格式
        _osLogger.logFormatter = [[SQLogFormatter alloc] init];
    }
    return _osLogger;
}

//文件寫入Logger
- (DDFileLogger *)fileLogger {
    if (!_fileLogger) {
        
        //使用自定義的logFileManager
        SQLogFileManager *fileManager = [[SQLogFileManager alloc] init];
        _fileLogger = [[DDFileLogger alloc] initWithLogFileManager:fileManager];
        //使用自定義的Logformatter
        _fileLogger.logFormatter = [[SQFileLogFormatter alloc] init];
        
        //重用log文件,不要每次啟動都創建新的log文件(默認值是NO)
        _fileLogger.doNotReuseLogFiles = NO;
        //log文件在24小時內有效,超過時間創建新log文件(默認值是24小時)
        _fileLogger.rollingFrequency = 60*60*24;
        //log文件的最大3M(默認值1M)
        _fileLogger.maximumFileSize = 1024*1024*3;
        //最多保存7個log文件(默認值是5)
        _fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
        //log文件夾最多保存10M(默認值是20M)
        _fileLogger.logFileManager.logFilesDiskQuota = 1014*1024*10;
    }
    return _fileLogger;
}

@end

參考

本文Demo
CocoaLumberjack Github地址
淺談iOS日志收集系統
日志庫CocoaLumberjack的整合過程
iOS平臺常見日志庫簡介

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容