iKcamp|基于Koa2搭建Node.js實(shí)戰(zhàn)(含視頻)? 記錄日志

滬江CCtalk視頻地址:https://www.cctalk.com/v/15114923883523

log.png

log 日志中間件

最困難的事情就是認(rèn)識(shí)自己霹琼。

在一個(gè)真實(shí)的項(xiàng)目中务傲,開(kāi)發(fā)只是整個(gè)投入的一小部分,版本迭代和后期維護(hù)占了極其重要的部分枣申。項(xiàng)目上線(xiàn)運(yùn)轉(zhuǎn)起來(lái)之后售葡,我們?nèi)绾沃理?xiàng)目運(yùn)轉(zhuǎn)的狀態(tài)呢?如何發(fā)現(xiàn)線(xiàn)上存在的問(wèn)題忠藤,如何及時(shí)進(jìn)行補(bǔ)救呢挟伙?記錄日志就是解決困擾的關(guān)鍵方案。正如我們每天寫(xiě)日記一樣模孩,不僅能夠記錄項(xiàng)目每天都做了什么尖阔,便于日后回顧,也可以將做錯(cuò)的事情記錄下來(lái)榨咐,進(jìn)行自我反省介却。完善的日志記錄不僅能夠還原問(wèn)題場(chǎng)景,還有助于統(tǒng)計(jì)訪(fǎng)問(wèn)數(shù)據(jù)块茁,分析用戶(hù)行為齿坷。

日志的作用

  • 顯示程序運(yùn)行狀態(tài)
  • 幫助開(kāi)發(fā)者排除問(wèn)題故障
  • 結(jié)合專(zhuān)業(yè)的日志分析工具(如 ELK )給出預(yù)警

關(guān)于編寫(xiě) log 中間件的預(yù)備知識(shí)

log4js

本項(xiàng)目中的 log 中間件是基于 log4js 2.x 的封裝,Log4jsNode.js 中一個(gè)成熟的記錄日志的第三方模塊数焊,下文也會(huì)根據(jù)中間件的使用介紹一些 log4js 的使用方法永淌。

日志分類(lèi)

日志可以大體上分為訪(fǎng)問(wèn)日志和應(yīng)用日志。訪(fǎng)問(wèn)日志一般記錄客戶(hù)端對(duì)項(xiàng)目的訪(fǎng)問(wèn)佩耳,主要是 http 請(qǐng)求遂蛀。這些數(shù)據(jù)屬于運(yùn)營(yíng)數(shù)據(jù),也可以反過(guò)來(lái)幫助改進(jìn)和提升網(wǎng)站的性能和用戶(hù)體驗(yàn)蚕愤;應(yīng)用日志是項(xiàng)目中需要特殊標(biāo)記和記錄的位置打印的日志答恶,包括出現(xiàn)異常的情況饺蚊,方便開(kāi)發(fā)人員查詢(xún)項(xiàng)目的運(yùn)行狀態(tài)和定位 bug 。應(yīng)用日志包含了debug悬嗓、info污呼、warnerror等級(jí)別的日志。

日志等級(jí)

log4js 中的日志輸出可分為如下7個(gè)等級(jí):

LOG_LEVEL.957353bf.png

在應(yīng)用中按照級(jí)別記錄了日志之后包竹,可以按照指定級(jí)別輸出高于指定級(jí)別的日志燕酷。

日志切割

當(dāng)我們的項(xiàng)目在線(xiàn)上環(huán)境穩(wěn)定運(yùn)行后,訪(fǎng)問(wèn)量會(huì)越來(lái)越大周瞎,日志文件也會(huì)越來(lái)越大苗缩。日益增大的文件對(duì)查看和跟蹤問(wèn)題帶來(lái)了諸多不便,同時(shí)增大了服務(wù)器的壓力声诸。雖然可以按照類(lèi)型將日志分為兩個(gè)文件酱讶,但并不會(huì)有太大的改善。所以我們按照日期將日志文件進(jìn)行分割彼乌。比如:今天將日志輸出到 task-2017-10-16.log 文件泻肯,明天會(huì)輸出到 task-2017-10-17.log 文件。減小單個(gè)文件的大小不僅方便開(kāi)發(fā)人員按照日期排查問(wèn)題慰照,還方便對(duì)日志文件進(jìn)行遷移灶挟。

代碼實(shí)現(xiàn)

安裝 log4js 模塊

npm i log4js -S

log4js 官方簡(jiǎn)單示例

middleware/ 目錄下創(chuàng)建 mi-log/demo.js,并貼入官方示例代碼:

var log4js = require('log4js');
var logger = log4js.getLogger();
logger.level = 'debug';
logger.debug("Some debug messages");

然后在 /middleware/mi-log/ 目錄下運(yùn)行:

cd ./middleware/mi-log/ && node demo.js

可以在終端看到如下輸出:

[2017-10-24 15:45:30.770] [DEBUG] default - Some debug messages

一段帶有日期毒租、時(shí)間稚铣、日志級(jí)別和調(diào)用 debug 方法時(shí)傳入的字符串的文本日志。實(shí)現(xiàn)了簡(jiǎn)單的終端日志輸出墅垮。

log4js 官方復(fù)雜示例

替換 mi-log/demo.js 中的代碼為如下:

const log4js = require('log4js');
log4js.configure({
  appenders: { cheese: { type: 'file', filename: 'cheese.log' } },
  categories: { default: { appenders: ['cheese'], level: 'error' } }
});

const logger = log4js.getLogger('cheese');
logger.trace('Entering cheese testing');
logger.debug('Got cheese.');
logger.info('Cheese is Gouda.');
logger.warn('Cheese is quite smelly.');
logger.error('Cheese is too ripe!');
logger.fatal('Cheese was breeding ground for listeria.');

再次在 /middleware/mi-log/ 目錄下運(yùn)行:

node demo.js

運(yùn)行之后惕医,在當(dāng)前的目錄下會(huì)生成一個(gè)日志文件 cheese.log文件,文件中有兩條日志并記錄了 error 及以上級(jí)別的信息噩斟,也就是如下內(nèi)容:

[2017-10-24 15:51:30.770] [ERROR] cheese - Cheese is too ripe!
[2017-10-24 15:51:30.774] [FATAL] cheese - Cheese was breeding ground for listeria.

注意: 日志文件產(chǎn)生的位置就是當(dāng)前啟動(dòng)環(huán)境的位置曹锨。

分析以上代碼就會(huì)發(fā)現(xiàn),configure 函數(shù)配置了日志的基本信息

{
  /**
   * 指定要記錄的日志分類(lèi) cheese
   * 展示方式為文件類(lèi)型 file
   * 日志輸出的文件名 cheese.log
   */
  appenders: { cheese: { type: 'file', filename: 'cheese.log' } },

  /**
   * 指定日志的默認(rèn)配置項(xiàng)
   * 如果 log4js.getLogger 中沒(méi)有指定剃允,默認(rèn)為 cheese 日志的配置項(xiàng)
   * 指定 cheese 日志的記錄內(nèi)容為 error 及 error 以上級(jí)別的信息
   */
  categories: { default: { appenders: ['cheese'], level: 'error' } }
}

改寫(xiě)為log中間件

創(chuàng)建 /mi-log/logger.js 文件沛简,并增加如下代碼:

const log4js = require('log4js');
module.exports = ( options ) => {
  return async (ctx, next) => {
    const start = Date.now()
    log4js.configure({
      appenders: { cheese: { type: 'file', filename: 'cheese.log' } },
      categories: { default: { appenders: ['cheese'], level: 'info' } }
    }); 
    const logger = log4js.getLogger('cheese');
    await next()
    const end = Date.now()
    const responseTime = end - start;
    logger.info(`響應(yīng)時(shí)間為${responseTime/1000}s`);
  }
}

創(chuàng)建 /mi-log/index.js 文件,并增加如下代碼:

const logger = require("./logger")
module.exports = () => {
   return logger()
}

修改 middleware/index.js 文件斥废,并增加對(duì) log 中間件的注冊(cè)椒楣, 如下代碼:

const path = require('path')
const bodyParser = require('koa-bodyparser')
const nunjucks = require('koa-nunjucks-2')
const staticFiles = require('koa-static')

const miSend = require('./mi-send')
// 引入日志中間件
const miLog = require('./mi-log')
module.exports = (app) => {
  // 注冊(cè)中間件
  app.use(miLog())

  app.use(staticFiles(path.resolve(__dirname, "../public")))
  app.use(nunjucks({
    ext: 'html',
    path: path.join(__dirname, '../views'),
    nunjucksConfig: {
      trimBlocks: true
    }
  }));
  app.use(bodyParser())
  app.use(miSend())
}

打開(kāi)瀏覽器并訪(fǎng)問(wèn) http://localhost:3000, 來(lái)發(fā)送一個(gè)http 請(qǐng)求牡肉。

如上捧灰,按照前幾節(jié)課程中講解的中間件的寫(xiě)法,將以上代碼改寫(xiě)為中間件。 基于 koa 的洋蔥模型毛俏,當(dāng) http 請(qǐng)求經(jīng)過(guò)此中間件時(shí)便會(huì)在 cheese.log 文件中打印一條日志級(jí)別為 info 的日志并記錄了請(qǐng)求的響應(yīng)時(shí)間炭庙。如此,便實(shí)現(xiàn)了訪(fǎng)問(wèn)日志的記錄煌寇。

實(shí)現(xiàn)應(yīng)用日志焕蹄,將其掛載到 ctx

若要在其他中間件或代碼中通過(guò) ctx 上的方法打印日志,首先需要在上下文中掛載 log 函數(shù)阀溶。打開(kāi) /mi-log/logger.js 文件:

const log4js = require('log4js');
const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"]

module.exports = () => {
  const contextLogger = {}
  log4js.configure({
    appenders: { cheese: { type: 'file', filename: 'cheese.log' } },
    categories: { default: { appenders: ['cheese'], level: 'info' } }
  }); 
 
  const logger = log4js.getLogger('cheese');
  
  return async (ctx, next) => {
     // 記錄請(qǐng)求開(kāi)始的時(shí)間
    const start = Date.now()
     // 循環(huán)methods將所有方法掛載到ctx 上
    methods.forEach((method, i) => {
       contextLogger[method] = (message) => {
         logger[method](message)
       }
    })
    ctx.log = contextLogger;

    await next()
    // 記錄完成的時(shí)間 作差 計(jì)算響應(yīng)時(shí)間
    const responseTime = Date.now() - start;
    logger.info(`響應(yīng)時(shí)間為${responseTime/1000}s`);
  }
}

創(chuàng)建 contextLogger 對(duì)象腻脏,將所有的日志級(jí)別方法賦給對(duì)應(yīng)的 contextLogger 對(duì)象方法。在將循環(huán)后的包含所有方法的 contextLogger 對(duì)象賦給 ctx 上的 log 方法银锻。

打開(kāi) /mi-send/index.js 文件永品, 并調(diào)用 ctx 上的 log 方法:

module.exports = () => {
  function render(json) {
      this.set("Content-Type", "application/json")
      this.body = JSON.stringify(json)
  }
  return async (ctx, next) => {
      ctx.send = render.bind(ctx)
      // 調(diào)用ctx上的log方法下的error方法打印日志
      ctx.log.error('ikcamp');
      await next()
  }
}

在其他中間件中通過(guò)調(diào)用 ctx 上的 log 方法,從而實(shí)現(xiàn)打印應(yīng)用日志。

const log4js = require('log4js');
const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"]

module.exports = () => {
  const contextLogger = {}
  const config = {
    appenders: {
        cheese: {
         type: 'dateFile', // 日志類(lèi)型 
         filename: `logs/task`,  // 輸出的文件名
         pattern: '-yyyy-MM-dd.log',  // 文件名增加后綴
         alwaysIncludePattern: true   // 是否總是有后綴名
       }
    },
    categories: {
      default: {
        appenders: ['cheese'],
        level:'info'
      }
    }
  }

  const logger = log4js.getLogger('cheese');

  return async (ctx, next) => {
    const start = Date.now()

    log4js.configure(config)
    methods.forEach((method, i) => {
      contextLogger[method] = (message) => {
        logger[method](message)
      }
    })
    ctx.log = contextLogger;

    await next()
    const responseTime = Date.now() - start;
    logger.info(`響應(yīng)時(shí)間為${responseTime/1000}s`);
  }
}

修改日志類(lèi)型為日期文件,按照日期切割日志輸出辜昵,以減小單個(gè)日志文件的大小。這時(shí)候打開(kāi)瀏覽器并訪(fǎng)問(wèn) http://localhost:3000症见,這時(shí)會(huì)自動(dòng)生成一個(gè) logs 目錄喂走,并生成一個(gè) cheese-2017-10-24.log 文件殃饿, 中間件執(zhí)行便會(huì)在其中中記錄下訪(fǎng)問(wèn)日志。

├── node_modules/
├── logs/ 
│     ├── cheese-2017-10-24.log 
├── ……
├── app.js

抽出可配置量

const log4js = require('log4js');
const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"]

// 提取默認(rèn)公用參數(shù)對(duì)象
const baseInfo = {
  appLogLevel: 'debug',  // 指定記錄的日志級(jí)別
  dir: 'logs',      // 指定日志存放的目錄名
  env: 'dev',   // 指定當(dāng)前環(huán)境芋肠,當(dāng)為開(kāi)發(fā)環(huán)境時(shí)乎芳,在控制臺(tái)也輸出,方便調(diào)試
  projectName: 'koa2-tutorial',  // 項(xiàng)目名帖池,記錄在日志中的項(xiàng)目信息
  serverIp: '0.0.0.0'       // 默認(rèn)情況下服務(wù)器 ip 地址
}

const { env, appLogLevel, dir } = baseInfo
module.exports = () => {
  const contextLogger = {}
  const appenders = {}
  
  appenders.cheese = {
    type: 'dateFile',
    filename: `${dir}/task`,
    pattern: '-yyyy-MM-dd.log',
    alwaysIncludePattern: true
  }
  // 環(huán)境變量為dev local development 認(rèn)為是開(kāi)發(fā)環(huán)境
  if (env === "dev" || env === "local" || env === "development") {
    appenders.out = {
      type: "console"
    }
  }
  let config = {
    appenders,
    categories: {
      default: {
        appenders: Object.keys(appenders),
        level: appLogLevel
      }
    }
  }

  const logger = log4js.getLogger('cheese');

  return async (ctx, next) => {
    const start = Date.now()

    log4js.configure(config)
    methods.forEach((method, i) => {
      contextLogger[method] = (message) => {
        logger[method](message)
      }
    })
    ctx.log = contextLogger;

    await next()
    const responseTime = Date.now() - start;
    logger.info(`響應(yīng)時(shí)間為${responseTime/1000}s`);
  }
}

代碼中奈惑,我們指定了幾個(gè)常量以方便后面提取,比如 appLogLevel睡汹、dir肴甸、env 等。 囚巴。并判斷當(dāng)前環(huán)境為開(kāi)發(fā)環(huán)境則將日志同時(shí)輸出到終端原在, 以便開(kāi)發(fā)人員在開(kāi)發(fā)是查看運(yùn)行狀態(tài)和查詢(xún)異常。

豐富日志信息

ctx 對(duì)象中彤叉,有一些客戶(hù)端信息是我們數(shù)據(jù)統(tǒng)計(jì)及排查問(wèn)題所需要的庶柿,所以完全可以利用這些信息來(lái)豐富日志內(nèi)容。在這里秽浇,我們只需要修改掛載 ctx 對(duì)象的 log 函數(shù)的傳入?yún)?shù):

logger[method](message)

參數(shù) message 是一個(gè)字符串浮庐,所以我們封裝一個(gè)函數(shù),用來(lái)把信息與上下文 ctx 中的客戶(hù)端信息相結(jié)合柬焕,并返回字符串审残。

增加日志信息的封裝文件 mi-log/access.js

module.exports = (ctx, message, commonInfo) => {
  const {
    method,  // 請(qǐng)求方法 get post或其他
    url,          // 請(qǐng)求鏈接
    host,     // 發(fā)送請(qǐng)求的客戶(hù)端的host
    headers   // 請(qǐng)求中的headers
  } = ctx.request;
  const client = {
    method,
    url,
    host,
    message,
    referer: headers['referer'],  // 請(qǐng)求的源地址
    userAgent: headers['user-agent']  // 客戶(hù)端信息 設(shè)備及瀏覽器信息
  }
  return JSON.stringify(Object.assign(commonInfo, client));
}

注意: 最終返回的是字符串梭域。

取出 ctx 對(duì)象中請(qǐng)求相關(guān)信息及客戶(hù)端 userAgent 等信息并轉(zhuǎn)為字符串。

mi-log/logger.js 文件中調(diào)用:

const log4js = require('log4js');
// 引入日志輸出信息的封裝文件
const access = require("./access.js");
const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"]

const baseInfo = {
  appLogLevel: 'debug',
  dir: 'logs',
  env: 'dev',
  projectName: 'koa2-tutorial',
  serverIp: '0.0.0.0'
}
const { env, appLogLevel, dir, serverIp, projectName } = baseInfo
// 增加常量搅轿,用來(lái)存儲(chǔ)公用的日志信息
const commonInfo = { projectName, serverIp }
module.exports = () => {
  const contextLogger = {}
  const appenders = {}

  appenders.cheese = {
    type: 'dateFile',
    filename: `${dir}/task`,
    pattern: '-yyyy-MM-dd.log',
    alwaysIncludePattern: true
  }
  
  if (env === "dev" || env === "local" || env === "development") {
    appenders.out = {
      type: "console"
    }
  }
  let config = {
    appenders,
    categories: {
      default: {
        appenders: Object.keys(appenders),
        level: appLogLevel
      }
    }
  }

  const logger = log4js.getLogger('cheese');

  return async (ctx, next) => {
    const start = Date.now()

    log4js.configure(config)
    methods.forEach((method, i) => {
      contextLogger[method] = (message) => {
       // 將入?yún)Q為函數(shù)返回的字符串
        logger[method](access(ctx, message, commonInfo))
      }
    })
    ctx.log = contextLogger;

    await next()
    const responseTime = Date.now() - start;
    logger.info(access(ctx, {
      responseTime: `響應(yīng)時(shí)間為${responseTime/1000}s`
    }, commonInfo))
  }
}

重啟服務(wù)器并訪(fǎng)問(wèn) http://localhost:3000 就會(huì)發(fā)現(xiàn)碰辅,日志文件的記錄內(nèi)容已經(jīng)變化。代碼到這里介时,已經(jīng)完成了大部分的日志功能没宾。下面我們完善下其他功能:自定義配置參數(shù)和捕捉錯(cuò)誤。

項(xiàng)目自定義內(nèi)容

安裝依賴(lài)文件 ip:

npm i ip -S

修改 middleware/index.js 中的調(diào)用方法

const path = require('path')
const ip = require('ip')
const bodyParser = require('koa-bodyparser')
const nunjucks = require('koa-nunjucks-2')
const staticFiles = require('koa-static')

const miSend = require('./mi-send')
const miLog = require('./mi-log/logger')
module.exports = (app) => {
  // 將配置中間件的參數(shù)在注冊(cè)中間件時(shí)作為參數(shù)傳入
  app.use(miLog({
    env: app.env,  // koa 提供的環(huán)境變量
    projectName: 'koa2-tutorial',
    appLogLevel: 'debug',
    dir: 'logs',
    serverIp: ip.address()
  }))

  app.use(staticFiles(path.resolve(__dirname, "../public")))

  app.use(nunjucks({
    ext: 'html',
    path: path.join(__dirname, '../views'),
    nunjucksConfig: {
      trimBlocks: true
    }
  }));

  app.use(bodyParser())
  app.use(miSend())
}

再次修改 mi-log/logger.js 文件:

const log4js = require('log4js');
const access = require("./access.js");
const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"]

const baseInfo = {
  appLogLevel: 'debug',
  dir: 'logs',
  env: 'dev',
  projectName: 'koa2-tutorial',
  serverIp: '0.0.0.0'
}

module.exports = (options) => {
  const contextLogger = {}
  const appenders = {}
  
  // 繼承自 baseInfo 默認(rèn)參數(shù)
  const opts = Object.assign({}, baseInfo, options || {})
  // 需要的變量解構(gòu) 方便使用
  const { env, appLogLevel, dir, serverIp, projectName } = opts
  const commonInfo = { projectName, serverIp }
    
  appenders.cheese = {
    type: 'dateFile',
    filename: `${dir}/task`,
    pattern: '-yyyy-MM-dd.log',
    alwaysIncludePattern: true
  }
  
  if (env === "dev" || env === "local" || env === "development") {
    appenders.out = {
      type: "console"
    }
  }
  let config = {
    appenders,
    categories: {
      default: {
        appenders: Object.keys(appenders),
        level: appLogLevel
      }
    }
  }

  const logger = log4js.getLogger('cheese');

  return async (ctx, next) => {
    const start = Date.now()

    log4js.configure(config)
    methods.forEach((method, i) => {
      contextLogger[method] = (message) => {
        logger[method](access(ctx, message, commonInfo))
      }
    })
    ctx.log = contextLogger;

    await next()
    const responseTime = Date.now() - start;
    logger.info(access(ctx, {
      responseTime: `響應(yīng)時(shí)間為${responseTime/1000}s`
    }, commonInfo))
  }
}

將項(xiàng)目中自定義的量覆蓋默認(rèn)值沸柔,解構(gòu)使用循衰。以達(dá)到項(xiàng)目自定義的目的。

對(duì)日志中間件進(jìn)行錯(cuò)誤處理

對(duì)于日志中間件里面的錯(cuò)誤褐澎,我們也需要捕獲并處理会钝。在這里,我們提取一層進(jìn)行封裝工三。

打開(kāi) mi-log/index.js 文件迁酸,修改代碼如下:

const logger = require("./logger")
module.exports = (options) => {
  const loggerMiddleware = logger(options)

  return (ctx, next) => {
    return loggerMiddleware(ctx, next)
    .catch((e) => {
        if (ctx.status < 500) {
            ctx.status = 500;
        }
        ctx.log.error(e.stack);
        ctx.state.logged = true;
        ctx.throw(e);
    })
  }
}

如果中間件里面有拋出錯(cuò)誤,這里將通過(guò) catch 函數(shù)捕捉到并處理俭正,將狀態(tài)碼小于 500 的錯(cuò)誤統(tǒng)一按照 500 錯(cuò)誤碼處理奸鬓,以方便后面的 http-error 中間件顯示錯(cuò)誤頁(yè)面。 調(diào)用 log 中間件打印堆棧信息并將錯(cuò)誤拋出到最外層的全局錯(cuò)誤監(jiān)聽(tīng)進(jìn)行處理掸读。

到這里我們的日志中間件已經(jīng)制作完成串远。當(dāng)然,還有很多的情況我們需要根據(jù)項(xiàng)目情況來(lái)繼續(xù)擴(kuò)展儿惫,比如結(jié)合『監(jiān)控系統(tǒng)』澡罚、『日志分析預(yù)警』和『自動(dòng)排查跟蹤機(jī)制』等∩銮耄可以參考一下官方文檔留搔。

下一節(jié)中,我們將學(xué)習(xí)下如何處理請(qǐng)求錯(cuò)誤铛铁。

移動(dòng)Web前端高效開(kāi)發(fā)實(shí)戰(zhàn).png

上一篇:iKcamp新課程推出啦~~~~~iKcamp|基于Koa2搭建Node.js實(shí)戰(zhàn)(含視頻)? 處理靜態(tài)資源

推薦: 翻譯項(xiàng)目Master的自述:

1. 干貨|人人都是翻譯項(xiàng)目的Master

2. iKcamp出品微信小程序教學(xué)共5章16小節(jié)匯總(含視頻)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末隔显,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子避归,更是在濱河造成了極大的恐慌荣月,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梳毙,死亡現(xiàn)場(chǎng)離奇詭異哺窄,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)萌业,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坷襟,“玉大人,你說(shuō)我怎么就攤上這事生年∮こ蹋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵抱婉,是天一觀(guān)的道長(zhǎng)档叔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蒸绩,這世上最難降的妖魔是什么衙四? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮患亿,結(jié)果婚禮上传蹈,老公的妹妹穿的比我還像新娘。我一直安慰自己步藕,他們只是感情好惦界,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著咙冗,像睡著了一般沾歪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乞娄,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天瞬逊,我揣著相機(jī)與錄音,去河邊找鬼仪或。 笑死,一個(gè)胖子當(dāng)著我的面吹牛士骤,可吹牛的內(nèi)容都是我干的范删。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拷肌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼到旦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起巨缘,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤添忘,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后若锁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體搁骑,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仲器。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煤率。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖乏冀,靈堂內(nèi)的尸體忽然破棺而出蝶糯,到底是詐尸還是另有隱情,我是刑警寧澤辆沦,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布昼捍,位于F島的核電站,受9級(jí)特大地震影響肢扯,放射性物質(zhì)發(fā)生泄漏端三。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一鹃彻、第九天 我趴在偏房一處隱蔽的房頂上張望郊闯。 院中可真熱鬧,春花似錦蛛株、人聲如沸团赁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)欢摄。三九已至,卻和暖如春笋粟,著一層夾襖步出監(jiān)牢的瞬間怀挠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工害捕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绿淋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓尝盼,卻偏偏與公主長(zhǎng)得像吞滞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盾沫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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