日志框架 Logback 官方手冊(cè)(第二章:架構(gòu))

以下內(nèi)容翻譯整理自logback官方手冊(cè)蒂胞,地址:logback官方手冊(cè)


logback 的架構(gòu)

logback的基本架構(gòu)足夠通用然磷,可以應(yīng)用于不同的環(huán)境。目前,logback分為三個(gè)模塊:logback-core喧笔,logback-classiclogback-access

core模塊是其它兩個(gè)模塊的基礎(chǔ)龟再,classic模塊繼承core模塊书闸,classic模塊相對(duì)log4j版本有顯著的改進(jìn),logback-classic天生實(shí)現(xiàn)了SLF4J API利凑,所以你可以在logback和其他日志框架之間自由切換浆劲,比如log4jJDK1.4引入的JUL(java.util.logging)嫌术。access模塊集成了Servlet容器,用來提供HTTP-access日志功能牌借,一個(gè)單獨(dú)的文檔包含訪問模塊文檔度气。

在本文檔的其余部分中,我們將引用logback-classic模塊來編寫logback膨报。

Logger磷籍,Appenders 和 Layouts

Logback基于三個(gè)主要類:LoggerAppenderLayout现柠,這三種類型的組件協(xié)同工作院领,使開發(fā)人員能夠根據(jù)消息類型和級(jí)別記錄消息,并在運(yùn)行時(shí)控制這些消息的格式和報(bào)告位置晒旅。

Logger類是logback-classic模塊的一部分栅盲,AppenderLayout接口是logback-core模塊的一部分。作為通用模塊废恋,logback-core沒有日志記錄器的概念。

logger是日志記錄器扒寄,appender是追加器鱼鼓,layout是布局。

Logger 上下文

任何日志API相對(duì)于普通的System.out.println的最重要的優(yōu)勢(shì)是能夠禁用某些日志語句该编,同時(shí)允許其他語句不受阻礙地打印迄本。該功能假定的日志空間是根據(jù)一些開發(fā)人員選擇的標(biāo)準(zhǔn)進(jìn)行分類的。在logback-classic中课竣,這種分類是logger的固有組成部分嘉赎。每個(gè)logger都附加到一個(gè)LoggerContext,該上下文負(fù)責(zé)生成logger于樟,并將它們安排在類似層次結(jié)構(gòu)的樹中公条。

logger是命名實(shí)體。它們的名字區(qū)分大小寫迂曲,并遵循分層命名規(guī)則:

如果一個(gè)logger的名稱后面跟著一個(gè)點(diǎn)靶橱,那么這個(gè)logger就是另一個(gè)logger的祖先。如果在其自身和后代logger之間沒有祖先路捧,則該logger被稱為子logger的父关霸。

例如,名稱為com.foologger是名稱為com.foo.Barlogger的父杰扫,類似地队寇,javajava.util的父,是java.util.Vector的祖先章姓。開發(fā)人員都應(yīng)該熟悉這種命名方案佳遣。

logger位于logger層次結(jié)構(gòu)的頂部炭序。它的特殊之處在于,它一開始就是每個(gè)層次結(jié)構(gòu)的一部分苍日。與每個(gè)logger一樣惭聂,可以通過它的名稱獲取它,如下所示:

Logger rootLogger =  LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

所有其他logger也可以通過org.slf4j.LoggerFactory類中的靜態(tài)方法getLogger()來獲取相恃。該方法需要傳遞日志記錄器的名稱作為參數(shù)辜纲。下面列出了Logger接口中的一些基本方法。

package org.slf4j; 
public interface Logger {
  String ROOT_LOGGER_NAME = "ROOT";
  // Printing methods: 
  public void trace(String message);
  public void debug(String message);
  public void info(String message); 
  public void warn(String message); 
  public void error(String message); 
}

有效級(jí)別(級(jí)別繼承)

logger可以被分配級(jí)別拦耐,可以設(shè)置的級(jí)別有TRACE, DEBUG, INFO, WARN, ERROR耕腾,這些級(jí)別別定義在ch.qos.logback.classic.Level類中,該類是final修飾的杀糯,不能被子類化扫俺。

如果一個(gè)給定的logger沒有被分配一個(gè)級(jí)別,那么它將從其最近的祖先那里繼承一個(gè)級(jí)別固翰。為了確保所有的logger最終都能繼承到一個(gè)級(jí)別狼纬,根logger有一個(gè)默認(rèn)級(jí)別DEBUG

下面是四個(gè)例子骂际,根據(jù)級(jí)別繼承規(guī)則疗琉,使用各種指定的級(jí)別值和產(chǎn)生的有效(繼承)級(jí)別。

示例1

Logger name 指定級(jí)別 有效級(jí)別
root DEBUG DEBUG
X none DEBUG
X.Y none DEBUG
X.Y.Z none DEBUG

示例1中歉铝,只有根logger被分配了一個(gè)級(jí)別盈简。這個(gè)級(jí)別是DEBUG,由其他logger繼承太示。X, X.Y, X.Y.Z柠贤。

示例2

Logger name 指定級(jí)別 有效級(jí)別
root ERROR ERROR
X INFO INFO
X.Y DEBUG DEBUG
X.Y.Z WARN WARN

示例2中,所有logger都有一個(gè)指定的級(jí)別值类缤,級(jí)別繼承不起作用臼勉。

示例3

Logger name 指定級(jí)別 有效級(jí)別
root DEBUG DEBUG
X INFO INFO
X.Y none INFO
X.Y.Z ERROR ERROR

示例3中,日志記錄器root, XX.Y.Z都有指定的級(jí)別呀非,X.Y沒有指定級(jí)別坚俗,是從父日志記錄器X繼承的級(jí)別。

示例4

Logger name 指定級(jí)別 有效級(jí)別
root DEBUG DEBUG
X INFO INFO
X.Y none INFO
X.Y.Z none INFO

示例4中岸裙,日志記錄器rootX有指定的級(jí)別猖败,X.YX.Y.Z沒有指定級(jí)別,從最近的有指定級(jí)別的父級(jí)X繼承級(jí)別值降允。

打印方法和基本選擇規(guī)則

根據(jù)定義恩闻,打印方法確定日志請(qǐng)求的級(jí)別。例如剧董,如果L是一個(gè)logger實(shí)例幢尚,那么語句L. INFO(“..”)就是一個(gè)級(jí)別INFO的日志語句破停。

如果日志記錄請(qǐng)求的級(jí)別高于或等于其日志記錄程序的有效級(jí)別,則啟用日志記錄請(qǐng)求尉剩。否則真慢,該請(qǐng)求將被禁用。如前所述理茎,沒有指定級(jí)別的日志記錄器將從其最近的祖先那里繼承一個(gè)級(jí)別黑界。這條規(guī)則是logback的核心。它規(guī)定各級(jí)的次序如下:
TRACE < DEBUG < INFO < WARN < ERROR
下面是一個(gè)基本選擇規(guī)則的例子皂林。

package com.wangbo.cto.logback;

import ch.qos.logback.classic.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @date 2019/9/13 22:48
 * @auther wangbo
 */
public class LogLevelTest {
    public static void main(String[] args) {
        //獲取一個(gè)名為“com.foo”的logger朗鸠,為了能設(shè)置級(jí)別,轉(zhuǎn)換為ch.qos.logback.classic.Logger類型
        ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
        //設(shè)置級(jí)別
        logger.setLevel(Level.INFO);

        //繼承最近的父com.foo的級(jí)別info
        Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");

        //warn >= info础倍,啟用此請(qǐng)求
        logger.warn("Low fuel level.");

        //debug <= info烛占,此請(qǐng)求已禁用
        logger.debug("Starting search for nearest gas station.");

        //info >= info,啟用此請(qǐng)求
        barlogger.info("Located nearest gas station.");

        //debug <= info沟启,此請(qǐng)求已禁用
        barlogger.debug("Exiting gas station search");
    }
}

運(yùn)行結(jié)果

22:59:44.139 [main] WARN com.foo - Low fuel level.
22:59:44.141 [main] INFO com.foo.Bar - Located nearest gas station.

獲取 Logger

調(diào)用LoggerFactory.getLogger忆家,相同名稱的方法將始終返回相同Logger對(duì)象的引用。例如:

Logger x = LoggerFactory.getLogger("wombat"); 
Logger y = LoggerFactory.getLogger("wombat");

XY是相同的Logger對(duì)象美浦。

因此弦赖,可以配置一個(gè)日志程序,然后在代碼的其他地方通過相同的名字獲取到相同的實(shí)例浦辨,而不需要傳遞引用。與生物學(xué)意義上的父母(父母總是先于子女)相反沼沈,logback日志記錄器可以按任何順序創(chuàng)建和配置流酬。特別是,父logger將發(fā)現(xiàn)并鏈接到它的后代列另,即使它是在它們之后實(shí)例化的芽腾。

通常在應(yīng)用程序初始化時(shí)配置logback環(huán)境。首選的方法是讀取配置文件页衙。不久將討論這種方法摊滔。

以日志記錄器所在的類命名日志記錄器似乎是迄今為止所知的最佳通用策略。

Appenders 和 Layouts

根據(jù)日志程序選擇性地啟用或禁用日志記錄請(qǐng)求的功能只是一部分店乐。Logback允許將日志請(qǐng)求打印到多個(gè)目的地艰躺。在logback中,輸出目的地稱為appender眨八。目前腺兴,針對(duì)控制臺(tái)、文件廉侧、遠(yuǎn)程套接字服務(wù)器页响、MySQL篓足、PostgreSQL、Oracle和其他數(shù)據(jù)庫闰蚕、JMS和遠(yuǎn)程UNIX Syslog守護(hù)進(jìn)程存在附加程序栈拖。

一個(gè)logger可以附加多個(gè)appender

addAppender方法向給定的logger添加一個(gè)appender没陡。對(duì)于給定的logger涩哟,每個(gè)啟用的日志請(qǐng)求都將被轉(zhuǎn)發(fā)到該logger中的所有appender以及層次結(jié)構(gòu)中更高的appender。換句話說诗鸭,appender是附加地從日志程序?qū)哟谓Y(jié)構(gòu)繼承的染簇。例如,如果將控制臺(tái)appender添加到根logger强岸,那么所有啟用的日志請(qǐng)求至少都將打印在控制臺(tái)上锻弓。此外,如果向logger(L)添加了一個(gè)文件appender蝌箍,然后青灼,為 L 和 L 的子節(jié)點(diǎn)啟用的日志記錄請(qǐng)求將打印在文件里和控制臺(tái)上。通過將loggeradditivity flag設(shè)置為false妓盲,可以覆蓋此默認(rèn)行為杂拨,使追加器積累不再是附加的。

下表是一個(gè)例子:

Logger Name 附加的 Appenders Additivity Flag 輸出目標(biāo) 注釋
root A1 不適用 A1 由于根日志程序位于日志程序?qū)哟谓Y(jié)構(gòu)的頂部悯衬,所以不應(yīng)用加法標(biāo)志弹沽。
x A-x1, A-x2 true A1, A-x1, A-x2 使用了 x 和 root 的追加器
x.y none true A1, A-x1, A-x2 使用了 x 和 root 的追加器
x.y.z A-xyz1 true A1, A-x1, A-x2, A-xyz1 使用了 x.y.z,x 和 root的追加器
security A-sec false A-sec 由于可加性標(biāo)志設(shè)置為 false筋粗,所以沒有追加器累加策橘,只會(huì)使用一個(gè)追加器 A-sec
security.access none true A-sec 因?yàn)?security 中的可加性標(biāo)志設(shè)置為 false,所以只使用 security 的追加器 A-sec

通常娜亿,用戶不僅希望自定義輸出目的地丽已,還希望自定義輸出格式÷蚓觯可以通過將layoutappender關(guān)聯(lián)來實(shí)現(xiàn)沛婴。layout負(fù)責(zé)根據(jù)用戶的意愿格式化日志請(qǐng)求,appender負(fù)責(zé)將格式化的輸出發(fā)送到它的目的地督赤。PatternLayout是標(biāo)準(zhǔn)logback分發(fā)版的一部分嘁灯,允許用戶根據(jù)類似于C語言printf函數(shù)的轉(zhuǎn)換模式指定輸出格式。

例如够挂,PatternLayout設(shè)置為%-4relative [%thread] %-5level %logger{32} - %msg%n旁仿,將輸出類似于下面格式的內(nèi)容:

176  [main] DEBUG manual.architecture.HelloWorld2 - Hello world.

第一個(gè)字段是自程序啟動(dòng)以來經(jīng)過的毫秒數(shù)。第二個(gè)字段是發(fā)出日志請(qǐng)求的線程。第三個(gè)字段是日志請(qǐng)求的級(jí)別枯冈。第四個(gè)字段是與日志請(qǐng)求關(guān)聯(lián)的日志記錄器的名稱毅贮。'-'后面的文本是請(qǐng)求的消息。

參數(shù)化日志

考慮到logback-classic中的logger實(shí)現(xiàn)了SLF4JLogger接口尘奏,某些打印方法允許多個(gè)參數(shù)滩褥。這些打印方法變體主要是為了提高性能,同時(shí)降低對(duì)代碼可讀性的影響炫加。

普通寫法

對(duì)于一些logger瑰煎,可以這樣寫:

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

該參數(shù)將整數(shù) i 和 entry[i] 轉(zhuǎn)換為字符串,并連接中間的字符串俗孝。會(huì)導(dǎo)致構(gòu)造消息參數(shù)的額外開銷酒甸,但是這與是否記錄消息沒有關(guān)系。

避免參數(shù)構(gòu)造額外開銷的一種方法是用一個(gè)測(cè)試包圍 log 語句赋铝。比如這樣:

if(logger.isDebugEnabled()) { 
  logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}

這樣插勤,如果logger禁用了DEBUG級(jí)別,就不會(huì)產(chǎn)生參數(shù)構(gòu)造的開銷革骨。另一方面农尖,如果logger啟用了DEBUG級(jí)別,系統(tǒng)將承擔(dān)兩次評(píng)估日志記錄器是否啟用的成本良哲,一次是在debugEnabled盛卡,第二次是在debug,在實(shí)踐中筑凫,這種開銷是微不足道的滑沧,因?yàn)樵u(píng)估一個(gè)日志記錄器所需時(shí)間相對(duì)于實(shí)際記錄一個(gè)請(qǐng)求所需的時(shí)間不到1%。

推薦寫法

存在一種基于消息格式的替代方法巍实。假設(shè)entry是一個(gè)對(duì)象嚎货,可以這樣寫:

Object entry = new SomeObject(); 
logger.debug("The entry is {}.", entry);

只有在評(píng)估是否進(jìn)行日志記錄之后,并且只有在決定記錄日志的情況下蔫浆,日志程序才會(huì)實(shí)現(xiàn)將消息格式化,并用條目的字符串值替換“{}”姐叁。換句話說瓦盛,當(dāng)禁用 log 語句時(shí),這種寫法不會(huì)產(chǎn)生參數(shù)構(gòu)造的成本外潜。
下面兩行代碼將產(chǎn)生完全相同的輸出原环。然而,在禁用日志語句的情況下处窥,第二種變體的性能至少比第一種變體好30倍嘱吗。

logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);

還有一種雙參數(shù)變體。例如,你可以這樣寫:

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);

如果需要傳遞三個(gè)或多個(gè)參數(shù)谒麦,還可以使用Object[]變體俄讹。例如,你可以這樣寫:

Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);

底層原理

在介紹了基本的logback組件之后绕德,現(xiàn)在可以描述當(dāng)用戶調(diào)用日志程序的打印方法時(shí)患膛,logback框架所采取的步驟。現(xiàn)在讓我們分析用戶調(diào)用名為com.wombat的日志記錄器的info()方法時(shí)耻蛇,logback所采取的步驟踪蹬。

1. 獲得過濾器鏈決策

如果存在,則調(diào)用TurboFilter鏈臣咖。Turbo 過濾器可以設(shè)置上下文范圍的閾值跃捣,或者根據(jù)與每個(gè)日志請(qǐng)求關(guān)聯(lián)的標(biāo)記、級(jí)別夺蛇、日志記錄器疚漆、消息或可拋出性等信息過濾掉某些事件。如果過濾器鏈的響應(yīng)是拒絕FilterReply.DENY蚊惯,則日志請(qǐng)求將被刪除愿卸。如果是中性FilterReply.NEUTRAL,然后我們繼續(xù)下一步截型,即第2步趴荸。如果是接受FilterReply.ACCEPT,我們跳過下一步宦焦,直接跳到步驟3发钝。

2. 應(yīng)用基本的選擇規(guī)則

在此步驟中,logback將日志記錄器的有效級(jí)別與請(qǐng)求的級(jí)別進(jìn)行比較波闹。如果根據(jù)此測(cè)試禁用日志記錄請(qǐng)求酝豪,那么logback將刪除該請(qǐng)求,而不進(jìn)行進(jìn)一步處理精堕。否則孵淘,將繼續(xù)下一步。

3. 創(chuàng)建一個(gè) LoggingEvent 對(duì)象

如果請(qǐng)求通過了前面的過濾器歹篓,logback將創(chuàng)建一個(gè)ch. qs .logback.classic.LoggingEvent對(duì)象瘫证,該對(duì)象包含請(qǐng)求的所有相關(guān)參數(shù),例如請(qǐng)求的日志記錄器庄撮,請(qǐng)求級(jí)別背捌,消息本身,可能隨請(qǐng)求一起傳遞的異常洞斯、當(dāng)前時(shí)間毡庆、當(dāng)前線程、發(fā)出日志記錄請(qǐng)求的類的各種數(shù)據(jù)以及 MDC。注意么抗,其中一些字段是延遲初始化的毅否,只有在實(shí)際需要時(shí)才會(huì)這樣做。MDC 用于用附加的上下文信息裝飾日志記錄請(qǐng)求乖坠。MDC將在下一章中討論搀突。

4. 調(diào)用 appenders

創(chuàng)建 LoggingEvent對(duì)象之后,logback將調(diào)用所有適用的appenderdoAppend()方法熊泵,即從日志程序上下文中繼承的appender仰迁。

logback發(fā)行版附帶的所有附加程序都擴(kuò)展了AppenderBase抽象類,該類在確保線程安全的同步塊中實(shí)現(xiàn)doAppend方法顽分。如果存在附加的自定義過濾器徐许,AppenderBasedoAppend()方法也能調(diào)用∽湔海可以動(dòng)態(tài)附加到任何附加器的自定義過濾器將在單獨(dú)的一章中介紹雌隅。

5. 格式化輸出

被調(diào)用的附加程序負(fù)責(zé)格式化日志事件。然而缸沃,一些(但不是所有)附加程序?qū)⒏袷交罩臼录娜蝿?wù)委托給了layout恰起,布局可以格式化LoggingEvent實(shí)例并以字符串的形式返回結(jié)果。注意趾牧,有些附加程序检盼,如SocketAppender,不將日志事件轉(zhuǎn)換為字符串翘单,而是序列化它吨枉。因此,它們沒有也不需要布局哄芜。

6. 發(fā)送 LoggingEvent

日志事件完全格式化后貌亭,由每個(gè)附加程序?qū)⑵浒l(fā)送到目的地。下面是一個(gè)序列 UML 圖认臊,展示了所有事情是如何工作的圃庭。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市失晴,隨后出現(xiàn)的幾起案子冤议,更是在濱河造成了極大的恐慌,老刑警劉巖师坎,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異堪滨,居然都是意外死亡胯陋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遏乔,“玉大人义矛,你說我怎么就攤上這事∶巳” “怎么了凉翻?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捻激。 經(jīng)常有香客問我制轰,道長,這世上最難降的妖魔是什么胞谭? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任垃杖,我火速辦了婚禮,結(jié)果婚禮上丈屹,老公的妹妹穿的比我還像新娘调俘。我一直安慰自己,他們只是感情好旺垒,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布彩库。 她就那樣靜靜地躺著,像睡著了一般先蒋。 火紅的嫁衣襯著肌膚如雪骇钦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天鞭达,我揣著相機(jī)與錄音司忱,去河邊找鬼。 笑死畴蹭,一個(gè)胖子當(dāng)著我的面吹牛坦仍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叨襟,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼繁扎,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了糊闽?” 一聲冷哼從身側(cè)響起梳玫,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎右犹,沒想到半個(gè)月后提澎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡念链,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年盼忌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了积糯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谦纱,死狀恐怖看成,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跨嘉,我是刑警寧澤川慌,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站祠乃,受9級(jí)特大地震影響梦重,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜跳纳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一忍饰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寺庄,春花似錦艾蓝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至馍盟,卻和暖如春于置,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贞岭。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國打工八毯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瞄桨。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓话速,卻偏偏與公主長得像,于是被迫代替她去往敵國和親芯侥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泊交,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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