日志庫`CocoaLumberjack`的整合過程

看了幾篇文章幕袱,一開始感覺整合第三方的日志庫CocoaLumberjack比較簡單凌净,不過真正落地寻狂,發(fā)現(xiàn)還是有幾個地方需要注意的邓萨。

下載代碼

CocoaLumberjack/CocoaLumberjack

  • 日志庫CocoaLumberjack目前已經(jīng)到了3.0.0版本,既支持Object-C锨天,也支持Swift毯盈。包管理方面,既支持CocoaPods病袄,也支持Carthage搂赋。根據(jù)當(dāng)前工程現(xiàn)狀,選擇Object-C + CocoaPods的方式
  • 在原有的Podfile中添加一行
    pod 'CocoaLumberjack', '~> 3.0.0'
    然后陪拘,將終端切換到工程目錄厂镇,執(zhí)行命令。帶上--no-repo-update是為了加快更新速度
    pod install --no-repo-update

基本需求

  • 可以設(shè)定Log 等級
  • 可以積攢到一定量的log 后左刽,一次性發(fā)送給服務(wù)器捺信,絕對不能打一個Log就發(fā)一次
  • 可以一定時間后帽驯,將未發(fā)送的log發(fā)送到服務(wù)器
  • 可以在App 切入后臺時將未發(fā)送的log 發(fā)送到服務(wù)器
    利用 CocoaLumberjack 搭建自己的 Log 系統(tǒng)
    這篇文章寫得很有代表性厢塘,這次也主要是按照這個來做。

接口設(shè)計

  • 一般的文章卓缰,都介紹在APPdelegate中添加代碼喇辽,這會導(dǎo)致這個類很亂掌挚,不是很好
  • 一版本的工程,都有自己的前綴菩咨,在工程里到處使用DDLog和整體氛圍不搭調(diào)吠式,最好在中間再包一層陡厘。
  • 由于是日志,大家都習(xí)慣了NSLog(frmt,...)這種可變參數(shù)形式的c風(fēng)格調(diào)用特占,而且一般還是宏定義的方式糙置。

引入一個單獨的類,采用(類方法 + 單例)的模式是目,簡化接口谤饭,保證只執(zhí)行一次

  • 日志是一種服務(wù),所以懊纳,文件命名為XXXLogService揉抵,作為一個中間隔離層。用戶不需要知道日志系統(tǒng)是怎么實現(xiàn)的嗤疯,用了哪個第三方庫
  • 提供一個類方法冤今,將初始化的代碼放在里面,在APPdelegate中只要一句調(diào)用就可以了茂缚,比如[XXXLogService start];
  • 以下是接口頭文件XXXLogService.h的內(nèi)容辟汰,將這個頭文件加入pch文件中,就可以在工程里方便使用XXXLog()
#import <Foundation/Foundation.h>
#import <CocoaLumberjack/CocoaLumberjack.h>

#ifdef DEBUG
static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
static const DDLogLevel ddLogLevel = DDLogLevelWarning;
#endif

// 默認(rèn)的宏阱佛,方便使用
#define XXXLog(frmt, ...)           XXXLogInfo(frmt, ...)

// 提供不同的宏,對應(yīng)到特定參數(shù)的對外接口
#define XXXLogError(frmt, ...)      DDLogError(frmt, ##__VA_ARGS__)
#define XXXLogWarning(frmt, ...)    DDLogWarn(frmt, ##__VA_ARGS__)
#define XXXLogInfo(frmt, ...)       DDLogInfo(frmt, ##__VA_ARGS__)
#define XXXLogDebug(frmt, ...)      DDLogDebug(frmt, ##__VA_ARGS__)
#define XXXLogVerbose(frmt, ...)    DDLogVerbose(frmt, ##__VA_ARGS__)


@interface XXXLogService : NSObject

+ (void)start;

@end
  • 以下是接口實現(xiàn)文件XXXLogService.m的內(nèi)容戴而,對外的類接口start只是一層封裝凑术。具體的實現(xiàn)在一個成員函數(shù)中,這里用了單例所意,將只執(zhí)行一次的內(nèi)容放在了init函數(shù)中淮逊。
    這個類只是一層封裝,沒有做具體的事情扶踊。
    內(nèi)容是從網(wǎng)上抄的泄鹏,這里準(zhǔn)備自定義一個loger往后臺傳日志,下面那么注釋為沒必要的內(nèi)容秧耗,實際使用時就直接刪除了备籽。至于顏色控件相關(guān)設(shè)置,其實也沒有必要分井,這里只是做個備忘车猬。
#import "XXXLogService.h"
#import "XXXLogger.h"
#import "XXXLogFormatter.h"

@implementation XXXLogService

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

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

- (instancetype)init {
    self = [super init];
    if (self) {
        // 自定義的log,要傳自己的后臺
        XXXLogger *logger = [[XXXLogger alloc] init];
        XXXLogFormatter *formatter = [[XXXLogFormatter alloc] init];
        
        [logger setLogFormatter:formatter];
        [DDLog addLogger:logger];
        
        // 注釋中一些不需要的代碼只是放在這里尺锚,見證一下歷史珠闰;正式使用時都應(yīng)該刪除。
        // XCode的log
        // XCode8之后不支持插件工作瘫辩,所以這些設(shè)置顏色的代碼不需要伏嗜,否則將會有無用的顏色信息混入log
        //開啟使用 XcodeColors
        setenv("XcodeColors", "YES", 0);
        //檢測
        char *xcode_colors = getenv("XcodeColors");
        if (xcode_colors && (strcmp(xcode_colors, "YES") == 0)) {
            // XcodeColors is installed and enabled!
            NSLog(@"XcodeColors is installed and enabled");
            //開啟DDLog 顏色
            [[DDTTYLogger sharedInstance] setColorsEnabled:YES];
            [[DDTTYLogger sharedInstance] setForegroundColor:[UIColor lightGrayColor] backgroundColor:nil forFlag:DDLogFlagVerbose];
            [[DDTTYLogger sharedInstance] setForegroundColor:[UIColor grayColor] backgroundColor:nil forFlag:DDLogFlagDebug];
            [[DDTTYLogger sharedInstance] setForegroundColor:[UIColor blueColor] backgroundColor:nil forFlag:DDLogFlagInfo];
            [[DDTTYLogger sharedInstance] setForegroundColor:[UIColor yellowColor] backgroundColor:nil forFlag:DDLogFlagWarning];
            [[DDTTYLogger sharedInstance] setForegroundColor:[UIColor redColor] backgroundColor:nil forFlag:DDLogFlagError];
        }
        // XCode的log坛悉,也用自定義的輸出格式
        [[DDTTYLogger sharedInstance] setLogFormatter:formatter];
        [DDLog addLogger:[DDTTYLogger sharedInstance]]; // TTY = Xcode console
        
        // DDASLLogger是輸出到mac終端,沒有必要再手機上用
        [DDLog addLogger:[DDASLLogger sharedInstance]]; // ASL = Apple System Logs

        // DDFileLogger是存在手機上承绸,在Cache目錄裸影,一般拿不出來,所以一般也沒什么大用
        DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
        fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
        fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
        [DDLog addLogger:fileLogger];
    }
    return self;
}
  • 刪除無用代碼八酒,精簡過后的init代碼如下
- (instancetype)init {
    self = [super init];
    if (self) {
        // 自定義的log空民,要傳自己的后臺
        XXXLogger *logger = [[WJSLogger alloc] init];
        XXXLogFormatter *formatter = [[WJSLogFormatter alloc] init];
        
        [logger setLogFormatter:formatter];
        [DDLog addLogger:logger];
        
        // XCode的log,也用自定義的輸出格式
        [[DDTTYLogger sharedInstance] setLogFormatter:formatter];
        [DDLog addLogger:[DDTTYLogger sharedInstance]]; // TTY = Xcode console
    }
    return self;
}

日志級別

  • 需要用一個靜態(tài)全局變量來定義日志級別
  • 日志級別是用來控制日志輸出的
  • 通過日志flag(前面定義的)和日志級別level(這里定義的)比較羞迷,決定是否輸出日志
  • 日志級別Error最高Verbose最低界轩,flag > level就輸出,否則就不輸出
#ifdef DEBUG
static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
static const DDLogLevel ddLogLevel = DDLogLevelWarning;
#endif

這樣定義的結(jié)果是:
DEBUG模式:所有的日志都輸出
其他模式:僅僅DDLogError()DDLogWarn()輸出衔瓮,其他的都沒有輸出

  • 這個變量的名字ddLogLevel最好命名為ddLogLevel浊猾,不然編譯不通過。原因是里面的宏定義用到了热鞍。
#ifndef LOG_LEVEL_DEF
    #define LOG_LEVEL_DEF ddLogLevel
#endif
  • 結(jié)合前面的宏定義葫慎,提供的方便方法,可以做到開發(fā)環(huán)境日志很全薇宠,而正式環(huán)境只搜集warningerror兩種日志
  • 在實現(xiàn)文件統(tǒng)一定義偷办,使用者不需要知道日志級別level這個概念,使用變得更簡單澄港。
// 默認(rèn)的宏椒涯,方便使用
#define XXXLog(frmt, ...)       XXXLogInfo(frmt, ...)

自定義格式

  • 如果需要自定義協(xié)議格式,那么需要實現(xiàn)DDLogFormatter協(xié)議的方法- (NSString *)formatLogMessage:(DDLogMessage *)logMessage;
/**
 *  This protocol describes the behavior of a log formatter
 */
@protocol DDLogFormatter <NSObject>
@required

/**
 * Formatters may optionally be added to any logger.
 * This allows for increased flexibility in the logging environment.
 * For example, log messages for log files may be formatted differently than log messages for the console.
 *
 * For more information about formatters, see the "Custom Formatters" page:
 * Documentation/CustomFormatters.md
 *
 * The formatter may also optionally filter the log message by returning nil,
 * in which case the logger will not log the message.
 **/
- (NSString * __nullable)formatLogMessage:(DDLogMessage *)logMessage;

@end
  • 下面是DDLogMessage的定義回梧,使用時一般用指針符號`->``引用內(nèi)部變量废岂,而給出的屬性都是可讀的,不能訪問狱意。不是非常理解這種設(shè)計意圖湖苞。
/**
 * The `DDLogMessage` class encapsulates information about the log message.
 * If you write custom loggers or formatters, you will be dealing with objects of this class.
 **/
@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;
}

/**
 *  Default `init` is not available
 */
- (instancetype)init NS_UNAVAILABLE;

/**
 * Standard init method for a log message object.
 * Used by the logging primitives. (And the macros use the logging primitives.)
 *
 * If you find need to manually create logMessage objects, there is one thing you should be aware of:
 *
 * If no flags are passed, the method expects the file and function parameters to be string literals.
 * That is, it expects the given strings to exist for the duration of the object's lifetime,
 * and it expects the given strings to be immutable.
 * In other words, it does not copy these strings, it simply points to them.
 * This is due to the fact that __FILE__ and __FUNCTION__ are usually used to specify these parameters,
 * so it makes sense to optimize and skip the unnecessary allocations.
 * However, if you need them to be copied you may use the options parameter to specify this.
 *
 *  @param message   the message
 *  @param level     the log level
 *  @param flag      the log flag
 *  @param context   the context (if any is defined)
 *  @param file      the current file
 *  @param function  the current function
 *  @param line      the current code line
 *  @param tag       potential tag
 *  @param options   a bitmask which supports DDLogMessageCopyFile and DDLogMessageCopyFunction.
 *  @param timestamp the log timestamp
 *
 *  @return a new instance of a log message model object
 */
- (instancetype)initWithMessage:(NSString *)message
                          level:(DDLogLevel)level
                           flag:(DDLogFlag)flag
                        context:(NSInteger)context
                           file:(NSString *)file
                       function:(NSString *)function
                           line:(NSUInteger)line
                            tag:(id)tag
                        options:(DDLogMessageOptions)options
                      timestamp:(NSDate *)timestamp NS_DESIGNATED_INITIALIZER;

/**
 * Read-only properties
 **/

/**
 *  The log message
 */
@property (readonly, nonatomic) NSString *message;
@property (readonly, nonatomic) DDLogLevel level;
@property (readonly, nonatomic) DDLogFlag flag;
@property (readonly, nonatomic) NSInteger context;
@property (readonly, nonatomic) NSString *file;
@property (readonly, nonatomic) NSString *fileName;
@property (readonly, nonatomic) NSString *function;
@property (readonly, nonatomic) NSUInteger line;
@property (readonly, nonatomic) id tag;
@property (readonly, nonatomic) DDLogMessageOptions options;
@property (readonly, nonatomic) NSDate *timestamp;
@property (readonly, nonatomic) NSString *threadID; // ID as it appears in NSLog calculated from the machThreadID
@property (readonly, nonatomic) NSString *threadName;
@property (readonly, nonatomic) NSString *queueLabel;

@end
- (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 = @"[VBOSE] >  ";
            break;
    }
    
    NSString *formatLog = [NSString stringWithFormat:@"%@[%@ %@][line %ld] %@",
                           logLevel, logMessage->_fileName, logMessage->_function,
                           logMessage->_line, logMessage->_message];
    return formatLog;
}

自定義的logger

根據(jù)網(wǎng)上內(nèi)容利用 CocoaLumberjack 搭建自己的 Log 系統(tǒng)修改而來

  • 從類DDAbstractDatabaseLogger繼承而來藏姐,需要包含頭文件#import <CocoaLumberjack/DDAbstractDatabaseLogger.h>
  • 這個類的作用是將log保存在數(shù)據(jù)庫中蚓再,這個類沒有暴露出來。相對來說包各,數(shù)據(jù)庫比文件系統(tǒng)操作要方便摘仅。所以用這個類,而不用文件類DDFileLogger
  • 保持默認(rèn)設(shè)置就好了问畅,達(dá)到500條或者間隔1分鐘就保存娃属;磁盤數(shù)據(jù)庫保留7天六荒,刪除操作間隔5分鐘。
  • 保存在數(shù)據(jù)庫中內(nèi)容取得不方便矾端,所以手機數(shù)據(jù)庫中的內(nèi)容我們不關(guān)心掏击,他能正常工作就好了。
  • 每一次執(zhí)行log秩铆,函數(shù)db_log就會執(zhí)行砚亭。在這里,我們把每條log都保存在一個數(shù)組中殴玛。比如@property (nonatomic, strong) NSMutableArray *logs;
  • 每一次保存log捅膘,函數(shù)db_save就會執(zhí)行。在這里滚粟,我們把緩存在數(shù)組中的log拼接成一個大字符串(\n分隔)寻仗,發(fā)送給后臺。向后臺發(fā)送成功后凡壤,清空這個緩存數(shù)組署尤。
  • 至于數(shù)據(jù)庫中的內(nèi)容,我們不用關(guān)心亚侠。
  • 監(jiān)聽系統(tǒng)消息UIApplicationWillResignActiveNotification曹体,在應(yīng)用回到后臺前,保存一下硝烂,向服務(wù)器發(fā)送一次混坞。
#import "XXXLogger.h"
#import "MyAFNetWorking.h"

// 達(dá)到500條就發(fā)送,所以緩存的數(shù)組最大容量達(dá)到2000的話钢坦,說明網(wǎng)絡(luò)出了問題
#define kLogCacheCapacity        2000

@interface XXXLogger ()

@property (nonatomic, strong) NSMutableArray *logs;

@end

@implementation XXXLogger

// 生命周期函數(shù)
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (instancetype)init {
    self = [super init];
    if (self) {
        self.logs = [NSMutableArray array];
        // 使用默認(rèn)的配置。達(dá)到500條或者間隔1分鐘就保存啥酱;磁盤數(shù)據(jù)庫保留7天爹凹,刪除操作間隔5分鐘,這兩個數(shù)據(jù)不關(guān)心镶殷,用基類的就可以了
        self.saveThreshold = 500; // 達(dá)到500條就保存?zhèn)骱笈_
        self.saveInterval = 60;   // 60s定時到就保存?zhèn)骱笈_
        // 監(jiān)聽UIApplicationWillResignActiveNotification消息禾酱,在程序進(jìn)入后臺前保存log
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
    }
    return self;
}

// 重寫父類函數(shù)
- (BOOL)db_log:(DDLogMessage *)logMessage {
    // _logFormatter只能用下劃線變量訪問,不能用self的方式绘趋,否則會觸發(fā)斷言
    if (!_logFormatter) {
        //沒有指定 formatter
        return NO;
    }
    
    if ([self.logs count] > kLogCacheCapacity) {
        // 如果段時間內(nèi)進(jìn)入大量log颤陶,并且遲遲發(fā)不到服務(wù)器上,我們可以判斷哪里出了問題陷遮,在這之后的 log 暫時不處理了滓走。
        // 但我們依然要告訴 DDLog 這個存進(jìn)去了。
        return YES;
    }
    //利用 formatter 得到消息字符串帽馋,添加到緩存
    @synchronized (self) {
        // _logFormatter只能用下劃線變量訪問搅方,不能用self的方式比吭,否則會觸發(fā)斷言
        [self.logs addObject:[_logFormatter formatLogMessage:logMessage]];
    }
    
    return YES;
}

- (void)db_save {
    //如果緩存內(nèi)沒數(shù)據(jù),啥也不做
    if (0 == [self.logs count]) {
        return;
    }
    
    // 用換行符姨涡,把所有的數(shù)據(jù)拼成一個大字符串
    NSString *logsString = [self.logs componentsJoinedByString:@"\n"];
    // 發(fā)送給服務(wù)器衩藤,將AFNetworking包一層作為網(wǎng)絡(luò)傳輸
    NSString *url = @"";  // 根據(jù)實際修改
    NSDictionary *logs = @{@"log": logsString}; // key值跟后臺商量好
    __weak __typeof(self) weakSelf = self;
    [[MyAFNetWorking shareAfnetworking] performRequestWithPath:url formDataDic:logs success:^(NSDictionary *responseObject) {
        // 已經(jīng)成功傳到服務(wù)器,之后將緩存清空
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        [strongSelf clearLogs];
    } failure:^(NSDictionary *responseObject) {
        // 啥也不做
    }];
}

// selector
- (void)onWillResignActive:(NSNotification *)notification {
    dispatch_async(self.loggerQueue, ^{
        [self db_save];
    });
}

// 清空緩存
- (void)clearLogs {
    @synchronized (self) {
        [self.logs removeAllObjects];
    }
}

@end

日志顏色

支持XCodeColors插件涛漂,根據(jù)日志等級顯示不同的顏色赏表。不過XCode8之后插件都不能用了,所以就不用折騰了匈仗。
下面是一些文章鏈接瓢剿,可以看看
robbiehanson/XcodeColors
Xcode8 插件失效不能用
Xcode升級后插件失效解決辦法
CocoaLumberjack使用

日志保存到阿里云

  • 日志可以發(fā)送到自己的后臺,怎么發(fā)送锚沸,和后臺商量好就行
  • 阿里云提供了日志服務(wù)器跋选,提供了日志發(fā)送的API,可以將客戶端的日志直接發(fā)送到阿里云哗蜈。運維可以通過工具查看前标,這樣就繞過后臺,也減輕了后臺的壓力距潘。
  • lujiajing1126/AliyunLogObjc
    這個是阿里提供的log上傳接口炼列,目前的熱度還很低
  • 支持Carthage,不支持CocoaPods音比,這個有點特別俭尖。
  • 如果工程還要支持iOS7,還是直接將源碼導(dǎo)入工程比較好洞翩,不需要庫管理工具稽犁。如果是Swift開發(fā)的工程,用Carthage就很方便骚亿。
#import <AliyunLogObjc/AliyunLogObjc.h> 
LogClient *client = [[LogClient alloc] initWithApp: @"endpoint" accessKeyID:@"" accessKeySecret:@"" projectName:@""];
LogGroup *logGroup = [[LogGroup alloc] initWithTopic: @"" andSource:@""];
Log *log1 = [[Log alloc] init];
[log1 PutContent: @"Value" withKey: @"Key"];
[logGroup PutLog:log1];
[client PostLog:logGroup logStoreName: @"" call:^(NSURLResponse* _Nullable response,NSError* _Nullable error) {
    if (error != nil) {
    }
}];
  • 僅有日志上傳功能已亥,并沒有日志本地管理功能±赐溃可以配合CocoaLumberjack使用虑椎。
  • 如果只是簡單將日志發(fā)送阿里云,單獨使用也是可以的俱笛。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捆姜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子迎膜,更是在濱河造成了極大的恐慌泥技,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磕仅,死亡現(xiàn)場離奇詭異零抬,居然都是意外死亡镊讼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門平夜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝶棋,“玉大人,你說我怎么就攤上這事忽妒⊥嫒梗” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵段直,是天一觀的道長吃溅。 經(jīng)常有香客問我,道長鸯檬,這世上最難降的妖魔是什么决侈? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮喧务,結(jié)果婚禮上赖歌,老公的妹妹穿的比我還像新娘。我一直安慰自己功茴,他們只是感情好庐冯,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坎穿,像睡著了一般展父。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上玲昧,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天栖茉,我揣著相機與錄音,去河邊找鬼孵延。 笑死吕漂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隙袁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼弃榨,長吁一口氣:“原來是場噩夢啊……” “哼菩收!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鲸睛,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤娜饵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后官辈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箱舞,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡遍坟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了晴股。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愿伴。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖电湘,靈堂內(nèi)的尸體忽然破棺而出隔节,到底是詐尸還是另有隱情,我是刑警寧澤寂呛,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布怎诫,位于F島的核電站,受9級特大地震影響贷痪,放射性物質(zhì)發(fā)生泄漏幻妓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一劫拢、第九天 我趴在偏房一處隱蔽的房頂上張望肉津。 院中可真熱鬧,春花似錦尚镰、人聲如沸阀圾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽初烘。三九已至,卻和暖如春分俯,著一層夾襖步出監(jiān)牢的瞬間肾筐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工缸剪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吗铐,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓杏节,卻偏偏與公主長得像唬渗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子奋渔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理镊逝,服務(wù)發(fā)現(xiàn),斷路器嫉鲸,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 我以一種最安靜的姿勢坐下 頭靠玻璃窗的上的月光 看著路燈 或是更高的某個地方 其實我第一次發(fā)現(xiàn) 燕大的夜景這么美 ...
    Rainer_zhang閱讀 240評論 0 0
  • 多年來,一直喜歡聽歌座菠,聽著不同的曲風(fēng)狸眼,歌聲里有年代,也有屬于那些年代里浴滴,塵封的過往拓萌,人們管這種情緒,叫做懷舊巡莹,而我...
    林桉閱讀 913評論 0 1