SLF4J深入剖析(涵蓋SLF4J 1.8)

1.SLF4J

SLF4J全稱 Simple Logging Facade for Java,它為Java下的日志系統(tǒng)提供了一套統(tǒng)一的門面(接口)豹缀。通過引入SLF4J,我們可以使項(xiàng)目的logging與logging具體的實(shí)現(xiàn)分離巾乳,在提供了一致的接口的同時(shí)滤愕,提供了靈活選擇logging實(shí)現(xiàn)的能力。

在SLF4J之前她肯,Apache Common Logging(即Jakarta Commons Logging佳头,簡稱JCL)也提供了類似的功能。它與SLF4J的區(qū)別在于:

  1. JCL即提供了統(tǒng)一的接口晴氨,也提供了一套默認(rèn)的實(shí)現(xiàn)康嘉;SLF4J則只提供了接口層
  2. JCL采用運(yùn)行時(shí)綁定,通過Classloader體系加載相應(yīng)的logging實(shí)現(xiàn)籽前;SLF4J采用了編譯期綁定
  3. SLF4J在接口易用性上更有優(yōu)勢亭珍,大大減少了不必要的日志拼接:
    • JCL下,為了避免無效的字符串拼接枝哄,一般需要按照如下方式輸出日志:
    if(log.isInfoEnabled()) {
        log.info("AnalyseOrderLogic.checkBusinessValid:" + channelCoopId
            + "," + JSON.toJSONString(entities));
    }
    
    • SLF4J則提供了占位符"{}"肄梨,只在必要的情況下才會進(jìn)行日志字符串處理和拼接:
    log.info("AnalyseOrderLogic.checkBusinessValid:{},{}", channelCoopId, JSON.toJSONString(entities));
    

2.SLF4J的使用

SLF4J的使用非常簡單:

  1. 引入SLF4J依賴
  2. 引入一種logging的SLF4J實(shí)現(xiàn),比如SLF4J LOG4J 12 Binding挠锥,或logback-classic

之后江滨,就可以正常使用SLF4J打印日志了窖剑,demo如下:

package some.package; 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public MyClass {
    Logger logger = LoggerFactory.getLogger(MyClass.class);
    
    public void someMethod() {
        logger.info("Hello world");
    }
}

讓我們從0開始搭建一個基于SLF4J的項(xiàng)目

2.1 引入SLF4J

【注】也可以從github上下載本節(jié)demo:

$ git clone git@github.com:jinluu/slf-demo.git
$ git checkout -b nop origin/nop
  1. 創(chuàng)建工程并引入slf4j-api依賴
$ mvn archetype:generate -DgroupId=cn.jinlu.slf.demo -DartifactId=slf-demo -Dversion=0.1-SNAPSHOT -DpackageName=cn.jinlu.slf.demo -DarchetypeArtifactId=maven-archetype-quickstart
  1. 引入slf4j-api依賴
    在pom.xml中添加依賴:
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.25</version>
    </dependency>
  1. 使用logger
package cn.jinlu.slf.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
    private static final Logger logger = LoggerFactory.getLogger(App.class);

    public static void main( String[] args )
    {
        logger.info( "Hello, {}!", App.class.getSimpleName());
    }
}

此時(shí)運(yùn)行App.main(),會發(fā)現(xiàn)沒有日志打印,但是有如下錯誤信息:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

出現(xiàn)該問題的原因是懂牧,SLF4J只提供了一個同一的日志接口/門面(Facade)蝙茶,如果找到任何實(shí)現(xiàn)宵呛,則綁定到默認(rèn)的NOPLoggerFactory茄厘。此時(shí)日志系統(tǒng)不會生效,而是打印出上述錯誤信息并繼續(xù)執(zhí)行稠茂。

因此柠偶,為了打印日志,我們還需要引入日志得實(shí)現(xiàn)類睬关。

看看此時(shí)的項(xiàng)目依賴關(guān)系:

$ mvn dependency:tree
...
[INFO] cn.jinlu.slf.demo:slf-demo:jar:0.1-SNAPSHOT
[INFO] +- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] \- junit:junit:jar:4.10:test
[INFO]    \- org.hamcrest:hamcrest-core:jar:1.1:test
...

2.2 引入Log4J作為SLF4J的實(shí)現(xiàn)

Logback是流行的log框架-Log4J的繼任者嚣州。相比Log4J,logback做了大量的改進(jìn)共螺,比如提供了更高的性能该肴,原生支持SLF4J等。

【注】也可以從github上下載本節(jié)demo:

$ git clone git@github.com:jinluu/slf-demo.git
$ git checkout -b log4j origin/log4j
  1. 引入log4j依賴
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.25</version>
    </dependency>
  1. src/main/resources下創(chuàng)建log4j.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
    <appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n" />
        </layout>
    </appender>

    <!-- 指定logger的設(shè)置藐不,additivity指示是否遵循缺省的繼承機(jī)制-->
    <logger name="cn.jinlu.slf.demo" additivity="false">
        <level value ="info"/>
        <appender-ref ref="myConsole" />
    </logger>

    <!-- 根logger的設(shè)置-->
    <root>
        <level value ="warn"/>
        <appender-ref ref="myConsole"/>
    </root>
</log4j:configuration>

再次運(yùn)行App.main()匀哄,可以看到如下日志輸出:

[29 17:18:45,209 INFO ] [main] demo.App - Hello, App!

最后秦效,檢查一下依賴關(guān)系:

$ mvn dependency:tree
[INFO] Scanning for projects...
...
[INFO] cn.jinlu.slf.demo:slf-demo:jar:0.1-SNAPSHOT
[INFO] +- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.7.25:compile
[INFO] |  \- log4j:log4j:jar:1.2.17:compile
[INFO] \- junit:junit:jar:4.10:test
[INFO]    \- org.hamcrest:hamcrest-core:jar:1.1:test

可見,slf4j-log4j12自動引入了log4j的實(shí)現(xiàn)log4j涎嚼。

2.2 將slf4j的實(shí)現(xiàn)修改為logback

【注】也可以從github上下載本節(jié)demo:

$ git clone git@github.com:jinluu/slf-demo.git
$ git checkout -b logback origin/logback

引入slf4j之后阱州,對日志實(shí)現(xiàn)的改動變得更加靈活,比如如果我們希望從log4j遷移到性能更好的logback法梯,那么我們可以:

  1. 修改依賴關(guān)系苔货,將對slf4j-log4j12依賴修改為對logback-classic的依賴:
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
  1. src/main/resources下刪除log4j.xml
    • 如果沒有配置文件,logback會默認(rèn)創(chuàng)建一個BasicConfigurator默認(rèn)配置立哑,將DEBUG級別及以上的日志輸出到Console夜惭。

再次運(yùn)行App.main(),可以看到如下日志輸出:

[29 17:18:45,209 INFO ] [main] demo.App - Hello, App!

此時(shí)工程的依賴關(guān)系如下铛绰,可見logback-classic自動引入了logback-core的實(shí)現(xiàn)诈茧。

$ mvn dependency:tree
...
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ slf-demo ---
[INFO] cn.jinlu.slf.demo:slf-demo:jar:0.1-SNAPSHOT
[INFO] +- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] |  \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] \- junit:junit:jar:4.10:test
[INFO]    \- org.hamcrest:hamcrest-core:jar:1.1:test

3.SLF4J靜態(tài)綁定源碼解析

在第一章中,我們指出捂掰,SLF4J相比JCL的一大優(yōu)勢是采用了靜態(tài)綁定敢会,避免了在OSGI等場景中通過classloader動態(tài)綁定造成的困擾。現(xiàn)在我們看看SLF4J靜態(tài)綁定的過程这嚣。

參考2.1節(jié)中App.java的代碼鸥昏,使用SLF4J時(shí),

  1. 通過LoggerFactory.getLogger(Class<?>)獲取一個Logger
private static final Logger logger = LoggerFactory.getLogger(App.class)
  1. 在該類內(nèi)部的任意處通過該logger打印不同級別的日志
logger.info( "Hello, {}!", App.class.getSimpleName());

首先看如何獲取一個Logger

3.1 創(chuàng)建或獲取一個Logger

一個典型的SLF4J類關(guān)系圖如下所示姐帚。這里我們忽略slf4j-api中的輔助類(位于包org.slf4j.helpers內(nèi))互广,以及不常用的MarkerMDC功能。

slf.plantuml.txt

就像demo代碼那樣卧土,SLF4J非常簡單。使用SLF4J只需要通過LoggerFactory.getLogger獲取一個Logger對象像樊,并通過該Logger對象進(jìn)行日志記錄即可尤莺。其他一切細(xì)節(jié),都通過SLF4J及SLF4J-XXX-binder進(jìn)行了屏蔽生棍。這個binder用來將具體的logging實(shí)現(xiàn)與SLF4J進(jìn)行綁定颤霎。

  • 在1.7及更早的版本中,該綁定都是通過繼承org.slf4j.spi中的接口來實(shí)現(xiàn)涂滴,并且SLF4J對繼承該接口的類的類名也進(jìn)行了約定友酱。因此圖中org.slf4j.impl包含了這些類的實(shí)現(xiàn),這些類都必須并且類名也必須遵照SLF4J的約定柔纵,且位于logging實(shí)現(xiàn)包中缔杉。比如以logback為例:
slf.plantuml.txt

在SLF4J的門面類中,會通過代碼硬編碼的方式獲取指定類名的單例(StaticXxxBinder)搁料,并通過org.slf4j.spi中的接口獲取相關(guān)的資源或详。

  • 在1.8版本中系羞,引入了SPI自動服務(wù)發(fā)現(xiàn)。具體請參考第4章霸琴。

3.1.1 LoggerFactory.getLogger(Class<?>)

調(diào)用LoggerFactory.getLogger(Class<?>)的源碼如下椒振。getLogger會通過類的全限定名從LoggerFactory工廠中獲取Logger。

public final class LoggerFactory {
    ...
    // 2.通過getILoggerFactory獲取或創(chuàng)建一個可用的LoggerFactory梧乘,并通過該Factory獲取或創(chuàng)建一個通過name指定的Logger澎迎。
    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }
    // 1.將clazz的全限定名作為String,調(diào)用geteLogger(String)方法
    public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        if (DETECT_LOGGER_NAME_MISMATCH) {
            // 如果開啟了檢測命名錯誤选调,那么如果clazz不存在夹供,則會打印錯誤信息。此處忽略相關(guān)處理
            ...
        }
        return logger;
    }
    
}

3.1.2 getILoggerFactory()創(chuàng)建Logger工廠

performInitialization()中学歧,SLF4J調(diào)用bind()進(jìn)行實(shí)現(xiàn)綁定罩引,如果綁定成功,則會進(jìn)行版本檢查枝笨。SLF4J要求slf4j-api的版本必須與其實(shí)現(xiàn)的版本對應(yīng)袁铐,否則可能會發(fā)生兼容性問題(SLF4J的1.8版與更早的版本,比如1.6和1.7存在兼容性問題)横浑。

綁定成功后剔桨,每次調(diào)用getILoggerFactory(),則會通過StaticLoggerBinder.getSingletion().getLoggerFactory()獲取一個ILoggerFactory接口派生的工廠對象徙融,用來創(chuàng)建具體的Logger實(shí)例洒缀。

public final class LoggerFactory {
    // 使用volatile的INITIALIZATION_STATE確保只會發(fā)生一次綁定
    static volatile int INITIALIZATION_STATE = UNINITIALIZED;
    ...
    
    // 3.綁定SLF4J實(shí)現(xiàn)
    private final static void performInitialization() {
        // 4.bind()方法是SLF4J實(shí)現(xiàn)綁定的關(guān)鍵
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            // 5.如果初始化成功,則檢查SLF4J的實(shí)現(xiàn)支持的版本號是否與SLF4J匹配
            // SLF4J要求binder的版本與slf4j-api的版本匹配欺冀,否則打印一條警告信息树绩,因?yàn)閟lf4j可能會不工作。
            versionSanityCheck();
        }
    }
    ...

    // 1.獲取ILoggerFactory的實(shí)現(xiàn)
    public static ILoggerFactory getILoggerFactory() {
        // 2.通過volatile的INITIALIZATION_STATE確保只會發(fā)生一次綁定
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            // 6.如果初始化成功隐轩,則調(diào)用StaticLoggerBinder單例的getLoggerFactory()方法獲得LoggerFactory工廠對象
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also http://jira.qos.ch/browse/SLF4J-97
            return SUBST_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }
}

3.1.3 bind()方法綁定SLF4J實(shí)現(xiàn)(初始化)

最后饺饭,來看一下bind()方法的實(shí)現(xiàn),注意代碼中的注釋职车。

在第2步中瘫俊,我們可以看到SLF4J靜態(tài)綁定的方式。它強(qiáng)制了StaticLoggerBinder的很多實(shí)現(xiàn)細(xì)節(jié):

  • 必須是一個單例
  • 必須提供一個靜態(tài)的getSingleton()方式創(chuàng)建/獲取單例
  • 類的全限定名必須是"org.slf4j.impl.StaticLoggerBinder"
  • 必須提供一個static final String REQUESTED_API_VERSION對象指定支持的版本

對其他幾個Binder:StaticMarkerBinder和StaticMDCBinder悴灵,SLF4J也有類似的強(qiáng)制實(shí)現(xiàn)要求扛芽。

public final class LoggerFactory {
    private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                // 1.針對非android系統(tǒng),通過ClassLoader尋址org/slf4j/impl/StaticLoggerBinder.class的可用實(shí)現(xiàn)积瞒。
                // 如果超過1個川尖,則發(fā)出警告信息。最終SLF4J會選擇其中的一個進(jìn)行綁定茫孔。
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            // 2.靜態(tài)綁定空厌,創(chuàng)建StaticLoggerBinder的單例
            StaticLoggerBinder.getSingleton();
            // 3.修改初始化狀態(tài)
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            // 4.報(bào)告實(shí)際綁定的StaticLoggerBinder信息
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstituteLoggers();
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }
}

4.SLF4J 1.8版的改進(jìn)

【注】也可以從github上下載本節(jié)demo:

$ git clone git@github.com:jinluu/slf-demo.git
$ git checkout -b slf4j18 origin/slf4j18

SLF4J 1.8中最大的改進(jìn)就是摒棄了hard code的代碼綁定(參考3.1.3庐船,注釋2),而是使用了更加優(yōu)雅嘲更、耦合更松的SPI方式進(jìn)行服務(wù)發(fā)現(xiàn)筐钟。我們看看1.8版本中對日志綁定的改進(jìn):

  1. 提供了org.slf4j.spi.SLF4JServiceProvider服務(wù)接口用于SPI綁定
  2. 改進(jìn)了org.slf4j.LoggerFactory.bind()的實(shí)現(xiàn),采用SPI方式進(jìn)行SLF4JServiceProvider服務(wù)發(fā)現(xiàn)和綁定
  3. 不再支持1.8版本以前的按照約定的類型StaticXxxBinder約定類名進(jìn)行綁定的方式
  4. 去除了3.1.3節(jié)中對StaticLoggerBinder的所有強(qiáng)制約定

可見赋朦,1.8版本和之前的版本是完全不兼容的篓冲,且1.8版本明顯更加優(yōu)雅。

4.1 SLF4JServiceProvider

類圖如下宠哄,只要將該接口的實(shí)現(xiàn)暴露成一個SPI服務(wù)壹将,SLF4J就可以正常綁定到該logging實(shí)現(xiàn)上。

slf.plantuml.txt

4.2 綁定

通過代碼及注釋毛嫉,可以發(fā)現(xiàn):

  1. bind()只會通過SPI服務(wù)發(fā)現(xiàn)的方式尋找可用的日志服務(wù)诽俯。
    因此,如果采用了1.8版本的slf4j-api承粤,則不支持1.8的日志實(shí)現(xiàn)不會被加載
  2. 如果發(fā)現(xiàn)了多于一個基于SPI的日志服務(wù)暴区,則打印告警,并默認(rèn)綁定第一個被發(fā)現(xiàn)的服務(wù)
  3. 如果沒有發(fā)現(xiàn)基于SPI的日志服務(wù)辛臊,則默認(rèn)綁定到SPI的NOP日志服務(wù)仙粱,并嘗試通過指定全限定名的方式(org.slf4j.impl.StaticLoggerBinder)尋址舊版本的日志服務(wù),如果找到了則發(fā)出版本mismatch告警彻舰,但是不會嘗試加載老版本的日志服務(wù)伐割。

因此,如果使用新版本的SLF4J(1.8及以上)刃唤,務(wù)必使用對應(yīng)的binder類隔心,避免引起兼容性問題

除了采用了更優(yōu)雅的服務(wù)發(fā)現(xiàn)機(jī)制尚胞,在其他方面硬霍,SLF4J 1.8與之前版本差別很小。

class LoggerFactory {
    private final static void performInitialization() {
        // 1.執(zhí)行綁定
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            // 2.成功辐真,則進(jìn)行版本檢查
            // 注意:老版本不支持SPI,壓根不會運(yùn)行到這里
            versionSanityCheck();
        }
    }
    private final static void bind() {
        try {
            // 3.SPI服務(wù)發(fā)現(xiàn)
            List<SLF4JServiceProvider> providersList = findServiceProviders();
            // 4.SPI發(fā)現(xiàn)多于一個日志實(shí)現(xiàn)則發(fā)出警告信息
            reportMultipleBindingAmbiguity(providersList);
            if (providersList != null && !providersList.isEmpty()) {
                // 5.綁定第一個被發(fā)現(xiàn)的logging服務(wù)
                PROVIDER = providersList.get(0);
                PROVIDER.initialize();
                INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                // 6.報(bào)告真實(shí)綁定的logging信息
                reportActualBinding(providersList);
                fixSubstituteLoggers();
                replayEvents();
                // release all resources in SUBST_FACTORY
                SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
            } else {
                // 7.如果通過SPI沒有發(fā)現(xiàn)可用服務(wù)崖堤,則默認(rèn)采用NOP日志
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("No SLF4J providers were found.");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_PROVIDERS_URL + " for further details.");

                // 8.嘗試尋找老版本的日志服務(wù)(通過尋址`org/slf4j/impl/StaticLoggerBinder.class`)
                // 找到則打印警告信息侍咱,但不會嘗試綁定老日志服務(wù)
                Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
            }
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }
}

4.3 logback對服務(wù)發(fā)現(xiàn)機(jī)制的改進(jìn)

目前最新的SLF4J 1.8版處于slf4j-api:1.8.0-beta-2版本,對應(yīng)的logback-classic版本為logback-classic:1.3.0-alpha4(官方對應(yīng)1.8.0-beta-1密幔,但是與beta-2兼容)楔脯。

為了兼容1.8的SLF4J,logback-classic提供了SPI服務(wù)配置文件胯甩,如下圖昧廷。這樣堪嫂,在啟動階段,SLF4J就可以通過ServiceLoader找到logback-classic并進(jìn)行注冊了木柬。

同時(shí)皆串,最新版的logback也去掉了org.slf.impl包,徹底摒棄了老版本SLF4J的支持眉枕。

image

4.4 SPI

關(guān)于SPI服務(wù)的深度剖析恶复,請參考筆者之前的博文『Service Provider Interface詳解 (SPI)


附錄

  1. SLF4j官網(wǎng)
  2. logback官網(wǎng)
  3. demo
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市速挑,隨后出現(xiàn)的幾起案子谤牡,更是在濱河造成了極大的恐慌,老刑警劉巖姥宝,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翅萤,死亡現(xiàn)場離奇詭異,居然都是意外死亡腊满,警方通過查閱死者的電腦和手機(jī)套么,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糜烹,“玉大人违诗,你說我怎么就攤上這事〈模” “怎么了诸迟?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長愕乎。 經(jīng)常有香客問我阵苇,道長,這世上最難降的妖魔是什么感论? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任绅项,我火速辦了婚禮,結(jié)果婚禮上比肄,老公的妹妹穿的比我還像新娘快耿。我一直安慰自己,他們只是感情好芳绩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布掀亥。 她就那樣靜靜地躺著,像睡著了一般妥色。 火紅的嫁衣襯著肌膚如雪搪花。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音撮竿,去河邊找鬼吮便。 笑死,一個胖子當(dāng)著我的面吹牛幢踏,可吹牛的內(nèi)容都是我干的髓需。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼惑折,長吁一口氣:“原來是場噩夢啊……” “哼授账!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起惨驶,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤白热,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后粗卜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屋确,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年续扔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了攻臀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡纱昧,死狀恐怖刨啸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情识脆,我是刑警寧澤设联,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站灼捂,受9級特大地震影響离例,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜悉稠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一宫蛆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧的猛,春花似錦耀盗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至猫牡,卻和暖如春胡诗,著一層夾襖步出監(jiān)牢的瞬間邓线,已是汗流浹背淌友。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工煌恢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人震庭。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓瑰抵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親器联。 傳聞我的和親對象是個殘疾皇子二汛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345