日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

探索Java日志的奥秘:底层日志系统-log4j2

發布時間:2024/8/23 windows 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 探索Java日志的奥秘:底层日志系统-log4j2 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

log4j2是apache在log4j的基礎上,參考logback架構實現的一套新的日志系統(我感覺是apache害怕logback了)。
log4j2的官方文檔上寫著一些它的優點:

  • 在擁有全部logback特性的情況下,還修復了一些隱藏問題
  • API 分離:現在log4j2也是門面模式使用日志,默認的日志實現是log4j2,當然你也可以用logback(應該沒有人會這么做)
  • 性能提升:log4j2包含下一代基于LMAX Disruptor library的異步logger,在多線程場景下,擁有18倍于log4j和logback的性能
  • 多API支持:log4j2提供Log4j 1.2, SLF4J, Commons Logging and java.util.logging (JUL) 的API支持
  • 避免鎖定:使用Log4j2 API的應用程序始終可以選擇使用任何符合SLF4J的庫作為log4j-to-slf4j適配器的記錄器實現
  • 自動重新加載配置:與Logback一樣,Log4j 2可以在修改時自動重新加載其配置。與Logback不同,它會在重新配置發生時不會丟失日志事件。
  • 高級過濾: 與Logback一樣,Log4j 2支持基于Log事件中的上下文數據,標記,正則表達式和其他組件進行過濾。
  • 插件架構: Log4j使用插件模式配置組件。因此,您無需編寫代碼來創建和配置Appender,Layout,Pattern Converter等。Log4j自動識別插件并在配置引用它們時使用它們。
  • 屬性支持:您可以在配置中引用屬性,Log4j將直接替換它們,或者Log4j將它們傳遞給將動態解析它們的底層組件。
  • Java 8 Lambda支持
  • 自定義日志級別
  • 產生垃圾少:在穩態日志記錄期間,Log4j 2 在獨立應用程序中是無垃圾的,在Web應用程序中是低垃圾。這減少了垃圾收集器的壓力,并且可以提供更好的響應時間性能。
  • 和應用server集成:版本2.10.0引入了一個模塊log4j-appserver,以改進與Apache Tomcat和Eclipse Jetty的集成。

Log4j2類圖:

這次從四個地方去探索源碼:啟動,配置,異步,插件化

源碼探索

啟動

log4j2的關鍵組件

  • LogManager

根據配置指定LogContexFactory,初始化對應的LoggerContext

  • LoggerContext

1、解析配置文件,解析為對應的java對象。
2、通過LoggerRegisty緩存Logger配置
3、Configuration配置信息
4、start方法解析配置文件,轉化為對應的java對象
5、通過getLogger獲取logger對象

  • Logger

LogManaer

該組件是Log4J啟動的入口,后續的LoggerContext以及Logger都是通過調用LogManager的靜態方法獲得。我們可以使用下面的代碼獲取Logger

Logger logger = LogManager.getLogger();

可以看出LogManager是十分關鍵的組件,因此在這個小節中我們詳細分析LogManager的啟動流程。
LogManager啟動的入口是下面的static代碼塊:

/*** Scans the classpath to find all logging implementation. Currently, only one will be used but this could be* extended to allow multiple implementations to be used.*/static {// Shortcut binding to force a specific logging implementation.final PropertiesUtil managerProps = PropertiesUtil.getProperties();final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);if (factoryClassName != null) {try {factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);} catch (final ClassNotFoundException cnfe) {LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);} catch (final Exception ex) {LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);}}if (factory == null) {final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();// note that the following initial call to ProviderUtil may block until a Provider has been installed when// running in an OSGi environmentif (ProviderUtil.hasProviders()) {for (final Provider provider : ProviderUtil.getProviders()) {final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();if (factoryClass != null) {try {factories.put(provider.getPriority(), factoryClass.newInstance());} catch (final Exception e) {LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider.getUrl(), e);}}}if (factories.isEmpty()) {LOGGER.error("Log4j2 could not find a logging implementation. "+ "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");factory = new SimpleLoggerContextFactory();} else if (factories.size() == 1) {factory = factories.get(factories.lastKey());} else {final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {sb.append("Factory: ").append(entry.getValue().getClass().getName());sb.append(", Weighting: ").append(entry.getKey()).append('\n');}factory = factories.get(factories.lastKey());sb.append("Using factory: ").append(factory.getClass().getName());LOGGER.warn(sb.toString());}} else {LOGGER.error("Log4j2 could not find a logging implementation. "+ "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");factory = new SimpleLoggerContextFactory();}}}

這段靜態代碼段主要分為下面的幾個步驟:

  • 首先根據特定配置文件的配置信息獲取loggerContextFactory
  • 如果沒有找到對應的Factory的實現類則通過ProviderUtil中的getProviders()方法載入providers,隨后通過provider的loadLoggerContextFactory方法載入LoggerContextFactory的實現類
  • 如果provider中沒有獲取到LoggerContextFactory的實現類或provider為空,則使用SimpleLoggerContextFactory作為LoggerContextFactory。
  • 根據配置文件載入LoggerContextFactory

    // Shortcut binding to force a specific logging implementation.final PropertiesUtil managerProps = PropertiesUtil.getProperties();final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);if (factoryClassName != null) {try {factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);} catch (final ClassNotFoundException cnfe) {LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);} catch (final Exception ex) {LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);}}

    在這段邏輯中,LogManager優先通過配置文件”log4j2.component.properties”通過配置項”log4j2.loggerContextFactory”來獲取LoggerContextFactory,如果用戶做了對應的配置,通過newCheckedInstanceOf方法實例化LoggerContextFactory的對象,最終的實現方式為:

    public static <T> T newInstanceOf(final Class<T> clazz)throws InstantiationException, IllegalAccessException, InvocationTargetException {try {return clazz.getConstructor().newInstance();} catch (final NoSuchMethodException ignored) {// FIXME: looking at the code for Class.newInstance(), this seems to do the same thing as abovereturn clazz.newInstance();}}

    在默認情況下,不存在初始的默認配置文件log4j2.component.properties,因此需要從其他途徑獲取LoggerContextFactory。

    通過Provider實例化LoggerContextFactory對象

    代碼:

    if (factory == null) {final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();// note that the following initial call to ProviderUtil may block until a Provider has been installed when// running in an OSGi environmentif (ProviderUtil.hasProviders()) {for (final Provider provider : ProviderUtil.getProviders()) {final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();if (factoryClass != null) {try {factories.put(provider.getPriority(), factoryClass.newInstance());} catch (final Exception e) {LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider.getUrl(), e);}}}if (factories.isEmpty()) {LOGGER.error("Log4j2 could not find a logging implementation. "+ "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");factory = new SimpleLoggerContextFactory();} else if (factories.size() == 1) {factory = factories.get(factories.lastKey());} else {final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {sb.append("Factory: ").append(entry.getValue().getClass().getName());sb.append(", Weighting: ").append(entry.getKey()).append('\n');}factory = factories.get(factories.lastKey());sb.append("Using factory: ").append(factory.getClass().getName());LOGGER.warn(sb.toString());}} else {LOGGER.error("Log4j2 could not find a logging implementation. "+ "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");factory = new SimpleLoggerContextFactory();}}

    這里比較有意思的是hasProviders和getProviders都會通過線程安全的方式去懶加載ProviderUtil這個對象。跟進lazyInit方法:

    protected static void lazyInit() {//noinspection DoubleCheckedLockingif (INSTANCE == null) {try {STARTUP_LOCK.lockInterruptibly();if (INSTANCE == null) {INSTANCE = new ProviderUtil();}} catch (final InterruptedException e) {LOGGER.fatal("Interrupted before Log4j Providers could be loaded.", e);Thread.currentThread().interrupt();} finally {STARTUP_LOCK.unlock();}}}

    再看構造方法:

    private ProviderUtil() {for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {loadProvider(resource.getUrl(), resource.getClassLoader());}}

    這里的懶加載其實就是懶加載Provider對象。在創建新的providerUtil實例的過程中就會直接實例化provider對象,其過程是先通過getClassLoaders方法獲取provider的類加載器,然后通過loadProviders(classLoader);加載類。在providerUtil實例化的最后,會統一查找”META-INF/log4j-provider.properties”文件中對應的provider的url,會考慮從遠程加載provider。而loadProviders方法就是在ProviderUtil的PROVIDERS列表中添加對一個的provider。可以看到默認的provider是org.apache.logging.log4j.core.impl.Log4jContextFactory

    LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory Log4jAPIVersion = 2.1.0 FactoryPriority= 10

    很有意思的是這里懶加載加上了鎖,而且使用的是
    lockInterruptibly這個方法。lockInterruptibly和lock的區別如下:

    lock 與 lockInterruptibly比較區別在于:

    lock 優先考慮獲取鎖,待獲取鎖成功后,才響應中斷。
    lockInterruptibly 優先考慮響應中斷,而不是響應鎖的普通獲取或重入獲取。
    ReentrantLock.lockInterruptibly允許在等待時由其它線程調用等待線程的
    Thread.interrupt 方法來中斷等待線程的等待而直接返回,這時不用獲取鎖,而會拋出一個InterruptedException。 ReentrantLock.lock方法不允許Thread.interrupt中斷,即使檢測到Thread.isInterrupted,一樣會繼續嘗試獲取鎖,失敗則繼續休眠。只是在最后獲取鎖成功后再把當前線程置為interrupted狀態,然后再中斷線程。

    上面有一句注釋值得注意:

    /*** Guards the ProviderUtil singleton instance from lazy initialization. This is primarily used for OSGi support.** @since 2.1*/protected static final Lock STARTUP_LOCK = new ReentrantLock();// STARTUP_LOCK guards INSTANCE for lazy initialization; this allows the OSGi Activator to pause the startup and// wait for a Provider to be installed. See LOG4J2-373private static volatile ProviderUtil INSTANCE;

    原來這里是為了讓osgi可以阻止啟動。
    再回到logManager:
    可以看到在加載完Provider之后,會做factory的綁定:

    if (factories.isEmpty()) {LOGGER.error("Log4j2 could not find a logging implementation. "+ "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");factory = new SimpleLoggerContextFactory();} else if (factories.size() == 1) {factory = factories.get(factories.lastKey());} else {final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {sb.append("Factory: ").append(entry.getValue().getClass().getName());sb.append(", Weighting: ").append(entry.getKey()).append('\n');}factory = factories.get(factories.lastKey());sb.append("Using factory: ").append(factory.getClass().getName());LOGGER.warn(sb.toString());}

    到這里,logmanager的啟動流程就結束了。

    配置

    在不使用slf4j的情況下,我們獲取logger的方式是這樣的:

    Logger logger = logManager.getLogger(xx.class)

    跟進getLogger方法:

    public static Logger getLogger(final Class<?> clazz) {final Class<?> cls = callerClass(clazz);return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls));}

    這里有一個getContext方法,跟進,

    public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) {try {return factory.getContext(FQCN, loader, null, currentContext);} catch (final IllegalStateException ex) {LOGGER.warn(ex.getMessage() + " Using SimpleLogger");return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext);}}

    上文提到factory的具體實現是Log4jContextFactory,跟進getContext
    方法:

    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,final boolean currentContext) {final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext);if (externalContext != null && ctx.getExternalContext() == null) {ctx.setExternalContext(externalContext);}if (ctx.getState() == LifeCycle.State.INITIALIZED) {ctx.start();}return ctx;}

    直接看start:

    public void start() {LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) {LOGGER.debug("Stack trace to locate invoker",new Exception("Not a real error, showing stack trace to locate invoker"));}if (configLock.tryLock()) {try {if (this.isInitialized() || this.isStopped()) {this.setStarting();reconfigure();if (this.configuration.isShutdownHookEnabled()) {setUpShutdownHook();}this.setStarted();}} finally {configLock.unlock();}}LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);}

    發現其中的核心方法是reconfigure方法,繼續跟進:

    private void reconfigure(final URI configURI) {final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",contextName, configURI, this, cl);final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);if (instance == null) {LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl);} else {setConfiguration(instance);/** instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) {* old.stop(); }*/final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource());LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",contextName, location, this, cl);}}

    可以看到每一個configuration都是從ConfigurationFactory拿出來的,我們先看看這個類的getInstance看看:

    public static ConfigurationFactory getInstance() {// volatile works in Java 1.6+, so double-checked locking also works properly//noinspection DoubleCheckedLockingif (factories == null) {LOCK.lock();try {if (factories == null) {final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>();final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);if (factoryClass != null) {addFactory(list, factoryClass);}final PluginManager manager = new PluginManager(CATEGORY);manager.collectPlugins();final Map<String, PluginType<?>> plugins = manager.getPlugins();final List<Class<? extends ConfigurationFactory>> ordered =new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size());for (final PluginType<?> type : plugins.values()) {try {ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));} catch (final Exception ex) {LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);}}Collections.sort(ordered, OrderComparator.getInstance());for (final Class<? extends ConfigurationFactory> clazz : ordered) {addFactory(list, clazz);}// see above comments about double-checked locking//noinspection NonThreadSafeLazyInitializationfactories = Collections.unmodifiableList(list);}} finally {LOCK.unlock();}}LOGGER.debug("Using configurationFactory {}", configFactory);return configFactory;}

    這里可以看到ConfigurationFactory中利用了PluginManager來進行初始化,PluginManager會將ConfigurationFactory的子類加載進來,默認使用的XmlConfigurationFactory,JsonConfigurationFactory,YamlConfigurationFactory這三個子類,這里插件化加載暫時按下不表。
    回到reconfigure這個方法,我們看到獲取ConfigurationFactory實例之后會去調用getConfiguration方法:

    public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) {if (!isActive()) {return null;}if (loader == null) {return getConfiguration(name, configLocation);}if (isClassLoaderUri(configLocation)) {final String path = extractClassLoaderUriPath(configLocation);final ConfigurationSource source = getInputFromResource(path, loader);if (source != null) {final Configuration configuration = getConfiguration(source);if (configuration != null) {return configuration;}}}return getConfiguration(name, configLocation);}

    跟進getConfiguration,這里值得注意的是有很多個getConfiguration,注意甄別,如果不確定的話可以通過debug的方式來確定。

    public Configuration getConfiguration(final String name, final URI configLocation) {if (configLocation == null) {final String config = this.substitutor.replace(PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));if (config != null) {ConfigurationSource source = null;try {source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config));} catch (final Exception ex) {// Ignore the error and try as a String.LOGGER.catching(Level.DEBUG, ex);}if (source == null) {final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();source = getInputFromString(config, loader);}if (source != null) {for (final ConfigurationFactory factory : factories) {final String[] types = factory.getSupportedTypes();if (types != null) {for (final String type : types) {if (type.equals("*") || config.endsWith(type)) {final Configuration c = factory.getConfiguration(source);if (c != null) {return c;}}}}}}}} else {for (final ConfigurationFactory factory : factories) {final String[] types = factory.getSupportedTypes();if (types != null) {for (final String type : types) {if (type.equals("*") || configLocation.toString().endsWith(type)) {final Configuration config = factory.getConfiguration(name, configLocation);if (config != null) {return config;}}}}}}Configuration config = getConfiguration(true, name);if (config == null) {config = getConfiguration(true, null);if (config == null) {config = getConfiguration(false, name);if (config == null) {config = getConfiguration(false, null);}}}if (config != null) {return config;}LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");return new DefaultConfiguration();}

    這里就會根據之前加載進來的factory進行配置的獲取,具體的不再解析。
    回到reconfigure,之后的步驟就是setConfiguration,入參就是剛才獲取的config

    private synchronized Configuration setConfiguration(final Configuration config) {Assert.requireNonNull(config, "No Configuration was provided");final Configuration prev = this.config;config.addListener(this);final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadExceptionmap.putIfAbsent("hostName", NetUtils.getLocalHostname());} catch (final Exception ex) {LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());map.putIfAbsent("hostName", "unknown");}map.putIfAbsent("contextName", name);config.start();this.config = config;updateLoggers();if (prev != null) {prev.removeListener(this);prev.stop();}firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));try {Server.reregisterMBeansAfterReconfigure();} catch (final Throwable t) {// LOG4J2-716: Android has no java.lang.managementLOGGER.error("Could not reconfigure JMX", t);}return prev;}

    這個方法最重要的步驟就是config.start,這才是真正做配置解析的

    public void start() {LOGGER.debug("Starting configuration {}", this);this.setStarting();pluginManager.collectPlugins(pluginPackages);final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);levelPlugins.collectPlugins(pluginPackages);final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();if (plugins != null) {for (final PluginType<?> type : plugins.values()) {try {// Cause the class to be initialized if it isn't already.Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader());} catch (final Exception e) {LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass().getSimpleName(), e);}}}setup();setupAdvertisement();doConfigure();final Set<LoggerConfig> alreadyStarted = new HashSet<LoggerConfig>();for (final LoggerConfig logger : loggers.values()) {logger.start();alreadyStarted.add(logger);}for (final Appender appender : appenders.values()) {appender.start();}if (!alreadyStarted.contains(root)) { // LOG4J2-392root.start(); // LOG4J2-336}super.start();LOGGER.debug("Started configuration {} OK.", this);}

    這里面有如下步驟:

  • 獲取日志等級的插件
  • 初始化
  • 初始化Advertiser
  • 配置
  • 先看一下初始化,也就是setup這個方法,setup是一個需要被復寫的方法,我們以XMLConfiguration作為例子,

    @Overridepublic void setup() {if (rootElement == null) {LOGGER.error("No logging configuration");return;}constructHierarchy(rootNode, rootElement);if (status.size() > 0) {for (final Status s : status) {LOGGER.error("Error processing element {}: {}", s.name, s.errorType);}return;}rootElement = null;}

    發現這里面有一個比較重要的方法constructHierarchy,跟進:

    private void constructHierarchy(final Node node, final Element element) {processAttributes(node, element);final StringBuilder buffer = new StringBuilder();final NodeList list = element.getChildNodes();final List<Node> children = node.getChildren();for (int i = 0; i < list.getLength(); i++) {final org.w3c.dom.Node w3cNode = list.item(i);if (w3cNode instanceof Element) {final Element child = (Element) w3cNode;final String name = getType(child);final PluginType<?> type = pluginManager.getPluginType(name);final Node childNode = new Node(node, name, type);constructHierarchy(childNode, child);if (type == null) {final String value = childNode.getValue();if (!childNode.hasChildren() && value != null) {node.getAttributes().put(name, value);} else {status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));}} else {children.add(childNode);}} else if (w3cNode instanceof Text) {final Text data = (Text) w3cNode;buffer.append(data.getData());}}final String text = buffer.toString().trim();if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {node.setValue(text);}}

    發現這個就是一個樹遍歷的過程。誠然,配置文件是以xml的形式給出的,xml的結構就是一個樹形結構。回到start方法,跟進doConfiguration:

    protected void doConfigure() {if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) {final Node first = rootNode.getChildren().get(0);createConfiguration(first, null);if (first.getObject() != null) {subst.setVariableResolver((StrLookup) first.getObject());}} else {final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);final StrLookup lookup = map == null ? null : new MapLookup(map);subst.setVariableResolver(new Interpolator(lookup, pluginPackages));}boolean setLoggers = false;boolean setRoot = false;for (final Node child : rootNode.getChildren()) {if (child.getName().equalsIgnoreCase("Properties")) {if (tempLookup == subst.getVariableResolver()) {LOGGER.error("Properties declaration must be the first element in the configuration");}continue;}createConfiguration(child, null);if (child.getObject() == null) {continue;}if (child.getName().equalsIgnoreCase("Appenders")) {appenders = child.getObject();} else if (child.isInstanceOf(Filter.class)) {addFilter(child.getObject(Filter.class));} else if (child.getName().equalsIgnoreCase("Loggers")) {final Loggers l = child.getObject();loggers = l.getMap();setLoggers = true;if (l.getRoot() != null) {root = l.getRoot();setRoot = true;}} else if (child.getName().equalsIgnoreCase("CustomLevels")) {customLevels = child.getObject(CustomLevels.class).getCustomLevels();} else if (child.isInstanceOf(CustomLevelConfig.class)) {final List<CustomLevelConfig> copy = new ArrayList<CustomLevelConfig>(customLevels);copy.add(child.getObject(CustomLevelConfig.class));customLevels = copy;} else {LOGGER.error("Unknown object \"{}\" of type {} is ignored.", child.getName(),child.getObject().getClass().getName());}}if (!setLoggers) {LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?");setToDefault();return;} else if (!setRoot) {LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");setToDefault();// return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers}for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {final LoggerConfig l = entry.getValue();for (final AppenderRef ref : l.getAppenderRefs()) {final Appender app = appenders.get(ref.getRef());if (app != null) {l.addAppender(app, ref.getLevel(), ref.getFilter());} else {LOGGER.error("Unable to locate appender {} for logger {}", ref.getRef(), l.getName());}}}setParents();}

    發現就是對剛剛獲取的configuration進行解析,然后塞進正確的地方。回到start方法,可以看到昨晚配置之后就是開啟logger和appender了。

    異步

    AsyncAppender

    log4j2突出于其他日志的優勢,異步日志實現。我們先從日志打印看進去。找到Logger,隨便找一個log日志的方法。

    public void debug(final Marker marker, final Message msg) {logIfEnabled(FQCN, Level.DEBUG, marker, msg, msg != null ? msg.getThrowable() : null);}

    一路跟進

    @PerformanceSensitive// NOTE: This is a hot method. Current implementation compiles to 29 bytes of byte code.// This is within the 35 byte MaxInlineSize threshold. Modify with care!private void logMessageTrackRecursion(final String fqcn,final Level level,final Marker marker,final Message msg,final Throwable throwable) {try {incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031tryLogMessage(fqcn, level, marker, msg, throwable);} finally {decrementRecursionDepth();}}

    可以看出這個在打日志之前做了調用次數的記錄。跟進tryLogMessage,

    @PerformanceSensitive// NOTE: This is a hot method. Current implementation compiles to 26 bytes of byte code.// This is within the 35 byte MaxInlineSize threshold. Modify with care!private void tryLogMessage(final String fqcn,final Level level,final Marker marker,final Message msg,final Throwable throwable) {try {logMessage(fqcn, level, marker, msg, throwable);} catch (final Exception e) {// LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the loggerhandleLogMessageException(e, fqcn, msg);}}

    繼續跟進:

    @Overridepublic void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,final Throwable t) {final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message;final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();strategy.log(this, getName(), fqcn, marker, level, msg, t);}

    這里可以看到在實際打日志的時候,會從config中獲取打日志的策略,跟蹤ReliabilityStrategy的創建,發現默認的實現類為DefaultReliabilityStrategy,跟進看實際打日志的方法

    @Overridepublic void log(final Supplier<LoggerConfig> reconfigured, final String loggerName, final String fqcn, final Marker marker, final Level level,final Message data, final Throwable t) {loggerConfig.log(loggerName, fqcn, marker, level, data, t);}

    這里實際打日志的方法居然是交給一個config去實現的。。。感覺有點奇怪。。跟進看看

    @PerformanceSensitive("allocation")public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,final Message data, final Throwable t) {List<Property> props = null;if (!propertiesRequireLookup) {props = properties;} else {if (properties != null) {props = new ArrayList<>(properties.size());final LogEvent event = Log4jLogEvent.newBuilder().setMessage(data).setMarker(marker).setLevel(level).setLoggerName(loggerName).setLoggerFqcn(fqcn).setThrown(t).build();for (int i = 0; i < properties.size(); i++) {final Property prop = properties.get(i);final String value = prop.isValueNeedsLookup() // since LOG4J2-1575? config.getStrSubstitutor().replace(event, prop.getValue()) //: prop.getValue();props.add(Property.createProperty(prop.getName(), value));}}}final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);try {log(logEvent, LoggerConfigPredicate.ALL);} finally {// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())ReusableLogEventFactory.release(logEvent);}}

    可以清楚的看到try之前是在創建LogEvent,try里面做的才是真正的log(好tm累),一路跟進。

    private void processLogEvent(final LogEvent event, LoggerConfigPredicate predicate) {event.setIncludeLocation(isIncludeLocation());if (predicate.allow(this)) {callAppenders(event);}logParent(event, predicate);}

    接下來就是callAppender了,我們直接開始看AsyncAppender的append方法:

    /*** Actual writing occurs here.** @param logEvent The LogEvent.*/@Overridepublic void append(final LogEvent logEvent) {if (!isStarted()) {throw new IllegalStateException("AsyncAppender " + getName() + " is not active");}final Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation);InternalAsyncUtil.makeMessageImmutable(logEvent.getMessage());if (!transfer(memento)) {if (blocking) {if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031// If queue is full AND we are in a recursive call, call appender directly to prevent deadlockAsyncQueueFullMessageUtil.logWarningToStatusLogger();logMessageInCurrentThread(logEvent);} else {// delegate to the event router (which may discard, enqueue and block, or log in current thread)final EventRoute route = asyncQueueFullPolicy.getRoute(thread.getId(), memento.getLevel());route.logMessage(this, memento);}} else {error("Appender " + getName() + " is unable to write primary appenders. queue is full");logToErrorAppenderIfNecessary(false, memento);}}}

    這里主要的步驟就是:

  • 生成logEvent
  • 將logEvent放入BlockingQueue,就是transfer方法
  • 如果BlockingQueue滿了則啟用相應的策略
  • 同樣的,這里也有一個線程用來做異步消費的事情

    private class AsyncThread extends Log4jThread {private volatile boolean shutdown = false;private final List<AppenderControl> appenders;private final BlockingQueue<LogEvent> queue;public AsyncThread(final List<AppenderControl> appenders, final BlockingQueue<LogEvent> queue) {super("AsyncAppender-" + THREAD_SEQUENCE.getAndIncrement());this.appenders = appenders;this.queue = queue;setDaemon(true);}@Overridepublic void run() {while (!shutdown) {LogEvent event;try {event = queue.take();if (event == SHUTDOWN_LOG_EVENT) {shutdown = true;continue;}} catch (final InterruptedException ex) {break; // LOG4J2-830}event.setEndOfBatch(queue.isEmpty());final boolean success = callAppenders(event);if (!success && errorAppender != null) {try {errorAppender.callAppender(event);} catch (final Exception ex) {// Silently accept the error.}}}// Process any remaining items in the queue.LOGGER.trace("AsyncAppender.AsyncThread shutting down. Processing remaining {} queue events.",queue.size());int count = 0;int ignored = 0;while (!queue.isEmpty()) {try {final LogEvent event = queue.take();if (event instanceof Log4jLogEvent) {final Log4jLogEvent logEvent = (Log4jLogEvent) event;logEvent.setEndOfBatch(queue.isEmpty());callAppenders(logEvent);count++;} else {ignored++;LOGGER.trace("Ignoring event of class {}", event.getClass().getName());}} catch (final InterruptedException ex) {// May have been interrupted to shut down.// Here we ignore interrupts and try to process all remaining events.}}LOGGER.trace("AsyncAppender.AsyncThread stopped. Queue has {} events remaining. "+ "Processed {} and ignored {} events since shutdown started.", queue.size(), count, ignored);}/*** Calls {@link AppenderControl#callAppender(LogEvent) callAppender} on all registered {@code AppenderControl}* objects, and returns {@code true} if at least one appender call was successful, {@code false} otherwise. Any* exceptions are silently ignored.** @param event the event to forward to the registered appenders* @return {@code true} if at least one appender call succeeded, {@code false} otherwise*/boolean callAppenders(final LogEvent event) {boolean success = false;for (final AppenderControl control : appenders) {try {control.callAppender(event);success = true;} catch (final Exception ex) {// If no appender is successful the error appender will get it.}}return success;}public void shutdown() {shutdown = true;if (queue.isEmpty()) {queue.offer(SHUTDOWN_LOG_EVENT);}if (getState() == State.TIMED_WAITING || getState() == State.WAITING) {this.interrupt(); // LOG4J2-1422: if underlying appender is stuck in wait/sleep/join/park call}}}

    直接看run方法:

  • 阻塞獲取logEvent
  • 將logEvent分發出去
  • 如果線程要退出了,將blockingQueue里面的event消費完在退出。
  • AsyncLogger

    直接從AsyncLogger的logMessage看進去:

    public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,final Throwable thrown) {if (loggerDisruptor.isUseThreadLocals()) {logWithThreadLocalTranslator(fqcn, level, marker, message, thrown);} else {// LOG4J2-1172: avoid storing non-JDK classes in ThreadLocals to avoid memory leaks in web appslogWithVarargTranslator(fqcn, level, marker, message, thrown);}}

    跟進logWithThreadLocalTranslator,

    private void logWithThreadLocalTranslator(final String fqcn, final Level level, final Marker marker,final Message message, final Throwable thrown) {// Implementation note: this method is tuned for performance. MODIFY WITH CARE!final RingBufferLogEventTranslator translator = getCachedTranslator();initTranslator(translator, fqcn, level, marker, message, thrown);initTranslatorThreadValues(translator);publish(translator);}

    這里的邏輯很簡單,就是將日志相關的信息轉換成RingBufferLogEvent(RingBuffer是Disruptor的無所隊列),然后將其發布到RingBuffer中。發布到RingBuffer中,那肯定也有消費邏輯。這時候有兩種方式可以找到這個消費的邏輯。

    • 找disruptor被使用的地方,然后查看,但是這樣做會很容易迷惑
    • 按照Log4j2的尿性,這種Logger都有對應的start方法,我們可以從start方法入手尋找

    在start方法中,我們找到了一段代碼:

    final RingBufferLogEventHandler[] handlers = {new RingBufferLogEventHandler()};disruptor.handleEventsWith(handlers);

    直接看看這個RingBufferLogEventHandler的實現:

    public class RingBufferLogEventHandler implementsSequenceReportingEventHandler<RingBufferLogEvent>, LifecycleAware {private static final int NOTIFY_PROGRESS_THRESHOLD = 50;private Sequence sequenceCallback;private int counter;private long threadId = -1;@Overridepublic void setSequenceCallback(final Sequence sequenceCallback) {this.sequenceCallback = sequenceCallback;}@Overridepublic void onEvent(final RingBufferLogEvent event, final long sequence,final boolean endOfBatch) throws Exception {event.execute(endOfBatch);event.clear();// notify the BatchEventProcessor that the sequence has progressed.// Without this callback the sequence would not be progressed// until the batch has completely finished.if (++counter > NOTIFY_PROGRESS_THRESHOLD) {sequenceCallback.set(sequence);counter = 0;}}/*** Returns the thread ID of the background consumer thread, or {@code -1} if the background thread has not started* yet.* @return the thread ID of the background consumer thread, or {@code -1}*/public long getThreadId() {return threadId;}@Overridepublic void onStart() {threadId = Thread.currentThread().getId();}@Overridepublic void onShutdown() {} }

    順著接口找上去,發現一個接口:

    /*** Callback interface to be implemented for processing events as they become available in the {@link RingBuffer}** @param <T> event implementation storing the data for sharing during exchange or parallel coordination of an event.* @see BatchEventProcessor#setExceptionHandler(ExceptionHandler) if you want to handle exceptions propagated out of the handler.*/ public interface EventHandler<T> {/*** Called when a publisher has published an event to the {@link RingBuffer}** @param event published to the {@link RingBuffer}* @param sequence of the event being processed* @param endOfBatch flag to indicate if this is the last event in a batch from the {@link RingBuffer}* @throws Exception if the EventHandler would like the exception handled further up the chain.*/void onEvent(T event, long sequence, boolean endOfBatch) throws Exception; }

    通過注釋可以發現,這個onEvent就是處理邏輯,回到RingBufferLogEventHandler的onEvent方法,發現里面有一個execute方法,跟進:

    public void execute(final boolean endOfBatch) {this.endOfBatch = endOfBatch;asyncLogger.actualAsyncLog(this);}

    這個方法就是實際打日志了,AsyncLogger看起來還是比較簡單的,只是使用了一個Disruptor。

    插件化

    之前在很多代碼里面都可以看到

    final PluginManager manager = new PluginManager(CATEGORY); manager.collectPlugins(pluginPackages);

    其實整個log4j2為了獲得更好的擴展性,將自己的很多組件都做成了插件,然后在配置的時候去加載plugin。
    跟進collectPlugins。

    public void collectPlugins(final List<String> packages) {final String categoryLowerCase = category.toLowerCase();final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<>();// First, iterate the Log4j2Plugin.dat files found in the main CLASSPATHMap<String, List<PluginType<?>>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader();if (builtInPlugins.isEmpty()) {// If we didn't find any plugins above, someone must have messed with the log4j-core.jar.// Search the standard package in the hopes we can find our core plugins.builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);}mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase));// Next, iterate any Log4j2Plugin.dat files from OSGi Bundlesfor (final Map<String, List<PluginType<?>>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) {mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase));}// Next iterate any packages passed to the static addPackage method.for (final String pkg : PACKAGES) {mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));}// Finally iterate any packages provided in the configuration (note these can be changed at runtime).if (packages != null) {for (final String pkg : packages) {mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));}}LOGGER.debug("PluginManager '{}' found {} plugins", category, newPlugins.size());plugins = newPlugins;}

    處理邏輯如下:

  • 從Log4j2Plugin.dat中加載所有的內置的plugin
  • 然后將OSGi Bundles中的Log4j2Plugin.dat中的plugin加載進來
  • 再加載傳入的package路徑中的plugin
  • 最后加載配置中的plugin
  • 邏輯還是比較簡單的,但是我在看源碼的時候發現了一個很有意思的東西,就是在加載log4j2 core插件的時候,也就是

    PluginRegistry.getInstance().loadFromMainClassLoader()

    這個方法,跟進到decodeCacheFiles:

    private Map<String, List<PluginType<?>>> decodeCacheFiles(final ClassLoader loader) {final long startTime = System.nanoTime();final PluginCache cache = new PluginCache();try {final Enumeration<URL> resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE);if (resources == null) {LOGGER.info("Plugin preloads not available from class loader {}", loader);} else {cache.loadCacheFiles(resources);}} catch (final IOException ioe) {LOGGER.warn("Unable to preload plugins", ioe);}final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<>();int pluginCount = 0;for (final Map.Entry<String, Map<String, PluginEntry>> outer : cache.getAllCategories().entrySet()) {final String categoryLowerCase = outer.getKey();final List<PluginType<?>> types = new ArrayList<>(outer.getValue().size());newPluginsByCategory.put(categoryLowerCase, types);for (final Map.Entry<String, PluginEntry> inner : outer.getValue().entrySet()) {final PluginEntry entry = inner.getValue();final String className = entry.getClassName();try {final Class<?> clazz = loader.loadClass(className);final PluginType<?> type = new PluginType<>(entry, clazz, entry.getName());types.add(type);++pluginCount;} catch (final ClassNotFoundException e) {LOGGER.info("Plugin [{}] could not be loaded due to missing classes.", className, e);} catch (final LinkageError e) {LOGGER.info("Plugin [{}] could not be loaded due to linkage error.", className, e);}}}final long endTime = System.nanoTime();final DecimalFormat numFormat = new DecimalFormat("#0.000000");final double seconds = (endTime - startTime) * 1e-9;LOGGER.debug("Took {} seconds to load {} plugins from {}",numFormat.format(seconds), pluginCount, loader);return newPluginsByCategory;}

    可以發現加載時候是從一個文件(PLUGIN_CACHE_FILE)獲取所有要獲取的plugin。看到這里的時候我有一個疑惑就是,為什么不用反射的方式直接去掃描,而是要從文件中加載進來,而且文件是寫死的,很不容易擴展啊。然后我找了一下PLUGIN_CACHE_FILE這個靜態變量的用處,發現了PluginProcessor這個類,這里用到了注解處理器。

    /*** Annotation processor for pre-scanning Log4j 2 plugins.*/ @SupportedAnnotationTypes("org.apache.logging.log4j.core.config.plugins.*") public class PluginProcessor extends AbstractProcessor {// TODO: this could be made more abstract to allow for compile-time and run-time plugin processing/*** The location of the plugin cache data file. This file is written to by this processor, and read from by* {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager}.*/public static final String PLUGIN_CACHE_FILE ="META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";private final PluginCache pluginCache = new PluginCache();@Overridepublic boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {System.out.println("Processing annotations");try {final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class);if (elements.isEmpty()) {System.out.println("No elements to process");return false;}collectPlugins(elements);writeCacheFile(elements.toArray(new Element[elements.size()]));System.out.println("Annotations processed");return true;} catch (final IOException e) {e.printStackTrace();error(e.getMessage());return false;} catch (final Exception ex) {ex.printStackTrace();error(ex.getMessage());return false;}} }

    (不太重要的方法省略)
    我們可以看到在process方法中,PluginProcessor會先收集所有的Plugin,然后在寫入文件。這樣做的好處就是可以省去反射時候的開銷。
    然后我又看了一下Plugin這個注解,發現它的RetentionPolicy是RUNTIME,一般來說PluginProcessor是搭配RetentionPolicy.SOURCE,CLASS使用的,而且既然你把自己的Plugin掃描之后寫在文件中了,RetentionPolicy就沒有必要是RUNTIME了吧,這個是一個很奇怪的地方。

    小結

    總算是把Log4j2的代碼看完了,發現它的設計理念很值得借鑒,為了靈活性,所有的東西都設計成插件式。互聯網技術日益發展,各種中間件層出不窮,而作為工程師的我們更需要做的是去思考代碼與代碼之間的關系,毫無疑問的是,解耦是最具有美感的關系。


    原文鏈接
    本文為云棲社區原創內容,未經允許不得轉載。

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的探索Java日志的奥秘:底层日志系统-log4j2的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    国产精品激情偷乱一区二区∴ | 一本大道久久精品懂色aⅴ 五月婷社区 | 中文字幕日韩有码 | 亚洲欧美日韩一二三区 | 亚洲日本va在线观看 | 国产裸体无遮挡 | 色资源在线 | av电影免费在线播放 | 亚洲国产精品500在线观看 | 成人av网址大全 | 日日干av | 日韩精品免费在线观看 | 亚洲影视九九影院在线观看 | 国产精品久久久 | 一区二区三区福利 | 国内久久视频 | 免费视频网 | 国产高清免费在线播放 | 国产一级片久久 | 午夜精品一区二区三区在线观看 | 天天操天天操天天操天天操天天操天天操 | 97超碰伊人 | 成人动漫精品一区二区 | 久久精国产 | 四虎伊人 | 国产一区二区视频在线 | 色视频网站在线观看一=区 a视频免费在线观看 | 91干干干 | 五月视频 | 狠狠的干狠狠的操 | 日韩成人av在线 | 美女福利视频一区二区 | 免费黄在线观看 | 最新av在线播放 | 精品人人人 | 婷婷丁香自拍 | 中文字幕在线视频免费播放 | 婷婷中文字幕在线观看 | 国产精品嫩草影视久久久 | 国产成人精品午夜在线播放 | 亚洲视频 在线观看 | 91禁在线看 | 精品久久美女 | 午夜在线免费观看视频 | 久草在线国产 | 国产成人免费 | 天天拍天天色 | 中文资源在线官网 | 国产精品美女在线观看 | 亚洲在线激情 | 日韩精品一卡 | 视频一区在线免费观看 | 欧美性生活免费看 | 天天色天天干天天色 | 免费观看成年人视频 | 亚洲免费视频观看 | 日韩在线色| 国产成人综合图片 | 色多多污污在线观看 | 亚洲免费在线观看视频 | 中中文字幕av在线 | av五月婷婷 | 91久久在线观看 | 丝袜美腿av | 亚洲精品字幕在线观看 | 亚洲精品免费播放 | 国产精品一区二区久久久 | 美女视频是黄的免费观看 | 中文字幕日韩无 | 国产在线2020 | 国产高清在线视频 | 国产精品精 | av综合av | 免费看成人av | av福利在线播放 | 99免费看片 | 久久久久看片 | 一级淫片在线观看 | 国产成人资源 | 国产高清在线免费视频 | 激情网站五月天 | 四虎在线免费视频 | 日韩精品短视频 | 一本一道久久a久久精品蜜桃 | 在线看一级片 | 97免费在线观看视频 | 久久影视一区 | 天天曰天天曰 | 西西大胆免费视频 | 婷婷黄色片| 日日夜夜操av | 99视频国产精品免费观看 | 三级在线国产 | 久久久2o19精品 | 在线小视频国产 | 久久久久久高清 | 99久久精品国产一区二区成人 | 免费一级毛毛片 | av在线播放一区二区三区 | 在线观看网站你懂的 | 亚洲精品国产品国语在线 | 成人影片在线免费观看 | 成人av免费电影 | 国内成人精品视频 | 久久精品三级 | 久久影视一区二区 | 91精品1区 | 91视频成人免费 | 久久躁日日躁aaaaxxxx | 国产一区91| 福利视频网址 | 午夜精品中文字幕 | 国产精品亚洲片在线播放 | 五月婷婷久草 | 去看片 | 五月婷婷av | 日韩a级黄色 | 中文字幕在线观看不卡 | 综合网五月天 | 91天天操 | 亚洲精品午夜国产va久久成人 | 国产精品不卡在线 | 一区二区中文字幕在线播放 | 国产精品6 | 爱射综合 | 水蜜桃亚洲一二三四在线 | 精品国产乱码久久久久 | 色综合久久综合网 | 五月天婷婷狠狠 | 中文字幕在线观看播放 | 免费视频网 | 色av资源网 | 色多多视频在线观看 | 91夫妻视频 | 美女精品| 探花视频免费观看 | 一本—道久久a久久精品蜜桃 | 中文字幕高清 | 国产精品久久久久国产精品日日 | 91中文字幕 | www.亚洲精品| 国产精品区二区三区日本 | 日韩精品一卡 | 亚洲无线视频 | 久草综合在线 | 嫩草伊人久久精品少妇av | 亚洲精品久久激情国产片 | 激情婷婷| 久视频在线| 97在线观 | 亚洲成av人片一区二区梦乃 | 狠狠躁18三区二区一区ai明星 | 性色av免费看 | 久久久亚洲国产精品麻豆综合天堂 | 国产91精品一区二区麻豆网站 | 在线日韩av| 91精品视频在线免费观看 | 色婷婷成人网 | 91成人蝌蚪| www国产亚洲精品久久网站 | 91精品成人| 天天综合亚洲 | 91视频 - x99av| 手机av电影在线观看 | 亚洲成人网在线 | 伊人色综合久久天天 | 狠狠狠狠狠操 | a天堂最新版中文在线地址 久久99久久精品国产 | 亚洲精品小区久久久久久 | 超碰国产在线观看 | 日韩av不卡在线 | 欧美黄色特级片 | 很黄很污的视频网站 | 久久精品9 | 黄色录像av | 日韩精选在线观看 | 在线观看中文字幕网站 | 日韩高清在线观看 | 精品视频免费播放 | 韩国三级一区 | 91麻豆精品91久久久久同性 | 特级毛片网 | 在线观看黄av | 国产剧情在线一区 | 欧美日韩三区二区 | 2020天天干天天操 | 天天综合入口 | 中文字幕乱码日本亚洲一区二区 | 成人a在线观看高清电影 | jizz999| 天天色综合久久 | wwwwwww黄| 色婷婷在线观看视频 | 天天操操 | 国产日韩视频在线播放 | 偷拍视频一区 | 久草在线视频网站 | 久久视频 | 午夜体验区 | 91精品在线免费视频 | 美女视频黄在线 | 色噜噜狠狠狠狠色综合 | 啪啪免费试看 | 91久久丝袜国产露脸动漫 | 97在线影院 | 日韩av福利在线 | 久久久久久视频 | 日本动漫做毛片一区二区 | 97精产国品一二三产区在线 | 最新国产福利 | 中文字幕欧美日韩va免费视频 | 三级av片 | 久久一区二区三区日韩 | 久久看毛片 | 探花视频在线版播放免费观看 | 一级精品视频在线观看宜春院 | 精品久久久久国产免费第一页 | www.天天成人国产电影 | 天天干 天天摸 天天操 | 午夜视频在线观看网站 | 日韩在线色| av先锋影音少妇 | 午夜电影中文字幕 | 麻豆久久| 狠狠狠色丁香婷婷综合激情 | 久久99热久久99精品 | 视频 天天草 | 人人爽爽人人 | 欧美aaa一级 | 国产精品k频道 | 国产理论免费 | 日韩影视在线 | 成x99人av在线www | 亚洲国产色一区 | 国产中文字幕国产 | 成人h在线播放 | 久久久综合香蕉尹人综合网 | a天堂一码二码专区 | 九九精品久久 | 精品美女国产在线 | 亚洲精品美女在线 | 999视频网站 | 天天综合天天做天天综合 | 国产精品乱码久久久久久1区2区 | 色综合久久久久网 | 四虎在线永久免费观看 | 午夜影院先 | 欧美日韩精品在线观看视频 | 可以免费观看的av片 | 亚洲精品成人在线 | 91免费视频网站在线观看 | 黄网站免费大全入口 | 午夜av免费在线观看 | 日韩精品中文字幕有码 | 天天操天天摸天天爽 | 国内精自线一二区永久 | 99久高清在线观看视频99精品热在线观看视频 | 狠狠干成人综合网 | 四虎国产永久在线精品 | 日韩簧片在线观看 | 2021久久| 色av资源网 | 亚洲成人软件 | 夜夜视频欧洲 | 99热这里有精品 | 久久综合免费视频影院 | 国产美女免费看 | 国产操在线 | 国产黄网站在线观看 | 91久久国产自产拍夜夜嗨 | 久久婷婷网 | 日日婷婷夜日日天干 | 91在线精品播放 | 天天综合五月天 | 夜夜夜影院 | 美女视频国产 | 国产成人三级一区二区在线观看一 | 国产黄免费 | 中文字幕日韩av | 在线日韩av | 江苏妇搡bbbb搡bbbb | 啪嗒啪嗒免费观看完整版 | 美女视频免费精品 | 精品久久久久久久久久久久久久久久 | 免费在线成人 | 欧美韩日在线 | 国产精品一区久久久久 | 青青河边草免费直播 | 五月婷婷电影网 | 91免费观看视频网站 | 天天射天天添 | 麻豆超碰| 午夜精品久久久久久99热明星 | 国产精品一区专区欧美日韩 | 午夜美女av| 国产精品成人一区二区 | 久久九九九九 | 久久国产亚洲 | 国产中文字幕免费 | 亚洲精品国产区 | 中文字幕在线看视频国产中文版 | 最近能播放的中文字幕 | 国产一区在线观看视频 | www.亚洲精品在线 | 性色av香蕉一区二区 | 伊人成人久久 | 国产亚洲精品久久久久5区 成人h电影在线观看 | 99精品视频免费看 | 欧美一区在线观看视频 | 欧美精品中文字幕亚洲专区 | 久久国产精品99久久久久久老狼 | 天天操天天射天天操 | 国产免费人成xvideos视频 | 久久综合五月天 | 色婷婷国产精品一区在线观看 | 亚洲做受高潮欧美裸体 | 超碰午夜| 超碰人人在线观看 | 国产黄色成人av | 亚洲精品456在线播放乱码 | 国产精品av久久久久久无 | 中文字幕成人网 | 国产专区精品视频 | 91亚洲视频在线观看 | 国产在线精品区 | 亚洲精品在线资源 | 国产福利网站 | 日韩欧美国产激情在线播放 | 999抗病毒口服液 | 狠狠躁天天躁综合网 | 国产99区| 青青射 | 天天干天天干天天操 | 福利视频一区二区 | 国产一级片免费观看 | 久久国产综合视频 | 伊人官网 | 亚洲天堂视频在线 | 中文字幕成人在线观看 | 欧美一级片 | 五月婷社区 | 天天在线免费视频 | 超碰在线日韩 | 丁香婷婷激情国产高清秒播 | 一二三区视频在线 | av网站播放 | 国产美女精彩久久 | www..com黄色片| 三级毛片视频 | 日韩视频精品在线 | 久草精品在线观看 | 国产成人一区二区在线观看 | 手机av永久免费 | 色综合久久久久久中文网 | 午夜成人影视 | 精品国产一区二区三区久久久蜜月 | 国产黄色a | 日韩影视大全 | 天天天天射| 丁香六月在线观看 | 欧美日韩精品在线观看视频 | 97超碰人人澡 | 成人精品亚洲 | 免费久久网站 | 在线视频18在线视频4k | 伊人精品在线 | 美女国内精品自产拍在线播放 | 日本黄色黄网站 | av怡红院| 在线电影a | zzijzzij亚洲成熟少妇 | 免费在线观看av网站 | 国产精品美女在线 | 91精品免费在线观看 | 91精品国产成 | 亚洲精品大片www | 亚洲国产成人精品电影在线观看 | 欧美xxxxx在线视频 | 免费看的黄色片 | 六月丁香婷婷在线 | 99精品色 | av三级在线免费观看 | 在线视频日韩精品 | 国产精品18久久久久久首页狼 | 91亚洲精品久久久蜜桃网站 | 波多野结衣日韩 | 国产一级视屏 | 国产亚洲精品bv在线观看 | 香蕉精品视频在线观看 | 国产免费观看久久黄 | 毛片a级片| 中文字幕日韩伦理 | 最近免费中文字幕 | 国产精品一区二区三区在线看 | 久久国产成人午夜av影院宅 | 成人一区在线观看 | 色综合久久综合网 | 99se视频在线观看 | 久久久国产精品人人片99精片欧美一 | 一区二区视频在线看 | 亚洲国产小视频在线观看 | 国产一级淫片在线观看 | av综合网址| 中文字幕在线免费97 | 97久久精品午夜一区二区 | 999国内精品永久免费视频 | 午夜国产福利在线 | 日本精品在线 | av综合在线观看 | 99精品在线免费视频 | 精品国产精品久久 | 国产高清在线免费观看 | 日韩aⅴ视频 | 国产精品一区二区中文字幕 | 国产亚洲在线视频 | 午夜黄网| 综合久久一本 | 日日夜夜精品网站 | 国产精品男女啪啪 | 亚洲理论在线 | 精品国产片 | 日韩,中文字幕 | 成人影视免费看 | 国产大片免费久久 | 欧美精品久久久久久久久老牛影院 | 日韩日韩日韩日韩 | 久久久www成人免费精品 | 91av大全| 久久99国产综合精品免费 | 91视频在线播放视频 | 天天曰天天 | 午夜国产一区 | 国产黄av | 亚洲精品视频免费在线 | 国产精华国产精品 | 久热超碰| 国产亚洲视频在线免费观看 | 色视频网页 | 久久久久福利视频 | 91福利视频在线 | 亚洲va综合va国产va中文 | 亚洲欧洲精品一区二区精品久久久 | 亚洲va欧洲va国产va不卡 | av在线电影网站 | 91片黄在线观 | 国产日产在线观看 | 日韩免费b | 日韩网站在线观看 | 91中文字幕网 | 国产一区二区不卡视频 | 久草综合视频 | 97视频资源| 视频国产一区二区三区 | 91福利在线观看 | 成人9ⅰ免费影视网站 | 久久久久久久av麻豆果冻 | 久久夜夜爽 | 婷婷伊人综合 | 麻豆国产网站入口 | 欧美不卡视频在线 | 97国产在线观看 | www.五月天婷婷.com | 久久视频一区 | 中文在线8资源库 | 国产精品男女视频 | 少妇做爰k8经典 | 久久精品网站视频 | 麻豆传媒在线视频 | 中文字幕网站视频在线 | 欧美大香线蕉线伊人久久 | 超级碰碰碰免费视频 | 国产午夜在线 | 久久国产精品久久久久 | 成人网色 | 99国产精品免费网站 | 国产手机视频在线播放 | www.色综合.com | 国产精品美女久久久久久久 | 99re中文字幕 | 九色精品免费永久在线 | 在线午夜电影神马影院 | 国产一区二区手机在线观看 | 青青草国产精品 | 中文字幕欧美日韩va免费视频 | 国产一卡久久电影永久 | 欧美在线观看视频一区二区三区 | 成人av高清 | 亚洲国产成人av网 | 婷婷日日 | 精品久久免费 | 国产精品久久久久久高潮 | 伊人丁香 | 天天插日日操 | www.五月婷婷.com | 色婷婷骚婷婷 | 天天干天天干 | 99热最新地址| 婷婷在线免费 | 日韩最新在线 | 国产精品永久在线观看 | 日韩中文字幕免费看 | 久久极品 | 欧美日韩国产一二 | 丁香六月婷婷开心 | 在线播放你懂 | 久久特级毛片 | 色九九在线 | 久久精品国产亚洲 | 亚洲久草网 | 国产成人av电影在线 | 91福利社区在线观看 | 狠狠狠狠狠狠天天爱 | 中文字幕免费成人 | 中文字幕在线观看免费高清电影 | 91色在线观看 | 国产1区2区 | av在线播放亚洲 | 久久美女免费视频 | 亚洲国产中文字幕 | 在线观看免费一级片 | 国产精品久久网 | 免费99视频 | 9热精品| av网站免费线看精品 | 97**国产露脸精品国产 | 欧美特一级 | 日本中文在线观看 | 欧美精品久久人人躁人人爽 | 丁香花在线观看免费完整版视频 | 成人av在线电影 | 国产黄色精品在线 | 亚洲欧美日本一区二区三区 | 亚洲激情六月 | 91香蕉视频720p | 亚洲韩国一区二区三区 | 久草影视在线观看 | 国产一区免费视频 | 色999在线 | 91人人人 | 日韩精品在线视频免费观看 | 免费在线国产 | 一区二区 精品 | 欧美一级高清片 | 欧美 亚洲 另类 激情 另类 | av不卡免费看 | 天天爽天天爽 | 国产原创在线视频 | 伊在线视频| 日本最新高清不卡中文字幕 | 一区二区三区免费 | 人人爽人人 | 成人免费中文字幕 | 亚洲国产午夜 | 日本99干网 | 手机在线中文字幕 | 亚洲毛片视频 | 在线播放国产一区二区三区 | 99视频一区 | 日韩精品一卡 | 免费看三级 | 亚洲成人家庭影院 | 中文在线最新版天堂 | 麻豆视频在线免费观看 | 2019中文最近的2019中文在线 | 精品在线一区二区 | 久久久久久久久影院 | 人成在线免费视频 | 欧美成人一区二区 | 色爱成人网 | 久久一区二区三区国产精品 | 欧美91av | 亚洲 欧洲 国产 精品 | 国产视频午夜 | 天天碰天天操视频 | 国产网红在线观看 | 国产精品免费久久久久久久久久中文 | 成年人黄色在线观看 | 五月婷在线 | 欧美日韩一二三四区 | 一区二区三区四区在线 | 国产传媒中文字幕 | 最近中文字幕完整高清 | 福利av在线 | 日本高清久久久 | 国产无遮挡又黄又爽在线观看 | 欧美激情综合五月色丁香小说 | 欧美性黑人 | 国产黄在线播放 | 四虎在线观看 | 亚洲欧洲国产视频 | 91刺激视频| 国产精品久久影院 | 精品国产乱码久久 | 日韩高清免费在线观看 | 91视频高清免费 | 婷婷射五月| 在线观看va | 久久久国产精品一区二区中文 | 色狠狠干 | 久久99国产综合精品免费 | 国产91在线观 | 午夜久久久久久久久 | 欧美日韩亚洲精品在线 | 中文字幕在线观看第二页 | 色国产精品一区在线观看 | 免费黄色小网站 | 中文字幕av全部资源www中文字幕在线观看 | 91成人精品一区在线播放69 | 国产 成人 久久 | 色婷婷综合在线 | 亚洲欧美日韩精品久久久 | 亚洲视频综合在线 | 91av在线视频播放 | 超碰999| 精品不卡视频 | 国产精品毛片一区二区 | 91chinese在线| 六月丁香婷婷网 | 亚洲欧洲精品久久 | 美女网站久久 | 国产四虎影院 | 五月天激情综合网 | 91自拍91| 欧美日韩亚洲在线观看 | 国产视频二区三区 | 日韩美女免费线视频 | 在线观看国产日韩 | 久久久久成 | 亚洲高清资源 | 久久99精品一区二区三区三区 | 91精品毛片 | 亚洲国产成人在线观看 | 亚洲一区二区高潮无套美女 | 99热在 | 91在线小视频 | 99性视频 | 99久热在线精品 | 亚洲自拍av在线 | 久久美女高清视频 | 天天综合色天天综合 | 欧美日韩高清免费 | 91麻豆免费版| 久久99精品久久久久蜜臀 | 婷婷亚洲综合五月天小说 | 久久精品国产免费看久久精品 | 亚洲一级二级 | 美女在线观看av | 少妇bbb搡bbbb搡bbbb′ | 亚洲国产片色 | 国产九色在线播放九色 | 日本三级大片 | 97福利在线观看 | 日韩精品首页 | 国产在线美女 | 亚洲精品 在线视频 | 欧美日韩高清一区 | 久久久国产一区二区三区四区小说 | 在线三级av | 日韩高清国产精品 | 精品一区二区三区四区在线 | 日韩激情精品 | 欧美日韩性生活 | 美女视频又黄又免费 | 国产91勾搭技师精品 | 看av在线| aa级黄色大片 | 亚州日韩中文字幕 | 亚洲伦理精品 | 日韩av成人在线观看 | 天天操天天操天天操天天操天天操 | 你操综合| 亚洲1级片 | 国产婷婷精品av在线 | 人人干狠狠干 | 91激情小视频 | 精品999久久久 | 欧美va天堂在线电影 | 国产 一区二区三区 在线 | 久久久国产精品一区二区三区 | 四虎精品成人免费网站 | 亚洲精品视频网站在线观看 | 日本中文字幕在线一区 | 97超级碰碰 | 五月天六月婷婷 | 国产亚洲精品bv在线观看 | 天天操天天操天天操天天操天天操 | 黄色电影在线免费观看 | 综合成人在线 | 狠狠操综合 | 日韩理论电影在线观看 | 黄色免费视频在线观看 | 久久久久久久福利 | 91免费视频黄 | av一级网站 | 色综合久久66 | 91尤物国产尤物福利在线播放 | 亚洲国产播放 | 综合久久综合久久 | 天天干,天天干 | 精品国产自在精品国产精野外直播 | 天天操天天操天天操天天操天天操天天操 | 久久视频二区 | 丁香视频免费观看 | 麻豆精品传媒视频 | 三级免费黄色 | 国产精品毛片一区视频 | 日本韩国精品在线 | 国产视频一区在线播放 | 国产亚洲日 | 日韩在线视频观看免费 | 日韩在线一区二区免费 | 免费又黄又爽的视频 | 美女精品 | 亚洲欧洲在线视频 | av电影中文| 欧美一级片在线免费观看 | 日本中文字幕在线 | 日韩免费在线观看视频 | 四虎在线免费观看 | 天天操夜夜做 | 激情综合五月天 | 欧美一级片免费观看 | 日韩欧美视频二区 | 人人玩人人添人人 | 国产成人精品久 | 国内视频在线 | 色婷婷97| 97精品超碰一区二区三区 | 在线 日韩 av | 在线观看的av | 久久久久成人免费 | 六月激情 | 国产精品aⅴ | 这里只有精品视频在线 | 国产亚洲91 | 欧美日本一二三 | 久久艹艹 | 久久99国产综合精品 | 欧美淫视频 | 狠狠操狠狠插 | 久爱精品在线 | 欧美成人性网 | 日韩精品中文字幕在线 | 久久不卡电影 | 国产手机在线视频 | 国产在线视频一区二区三区 | 激情导航 | 青青久草在线 | 亚洲精品www. | 日韩成人av在线 | 在线观看精品 | 444av| 干干干操操操 | 热久久在线视频 | 国产小视频免费在线观看 | 91视频中文字幕 | 男女男视频 | 成年人免费看av | 免费特级黄色片 | 在线视频 国产 日韩 | av不卡网站 | 一区二区三区高清不卡 | 国产精品一区二区三区视频免费 | 国产va在线观看免费 | 国产精品久久久久久五月尺 | 黄色app网站在线观看 | 91中文字幕在线 | 97精品国产97久久久久久春色 | 欧美日韩国产区 | 日日色综合 | 亚洲国产日韩一区 | 婷婷色网址 | 在线观影网站 | 国产精品一区久久久久 | 天天色影院 | 手机在线看a | 在线色亚洲 | 亚洲五月激情 | 成人一区二区三区在线 | 精品成人国产 | 91日韩在线专区 | 91一区啪爱嗯打偷拍欧美 | 狠狠躁日日躁狂躁夜夜躁 | 色姑娘综合网 | 精品 激情| 国产又粗又猛又爽 | 精品国产乱码一区二 | 99精品偷拍视频一区二区三区 | 欧美成年性| 亚洲 欧洲av | 国产精品一区二区三区免费看 | 婷婷五月情 | 伊人官网 | 久久综合中文色婷婷 | 国产麻豆精品免费视频 | 亚洲三级性片 | 日韩二区在线观看 | 一本一本久久a久久精品综合小说 | 亚洲国产精品久久 | 99在线热播 | 中文久久精品 | 人人爽人人爽人人片 | 国产成人精品久久久 | 一本一本久久a久久精品综合 | 91精品伦理| 日韩久久一区 | 不卡的av在线 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 99视频这里只有 | 最新日韩在线观看视频 | 麻豆首页 | 激情影音先锋 | 超碰午夜 | av在线色 | 亚洲一级理论片 | 久久久美女 | 国产一区二区三区黄 | 亚洲尺码电影av久久 | 亚洲天堂精品视频在线观看 | 国产99久久99热这里精品5 | 热久久电影 | 91精品国产成人观看 | 国产视频在线免费 | 婷婷深爱五月 | 欧美日韩视频 | 国产91丝袜在线播放动漫 | 日本黄色免费大片 | 成人手机在线视频 | 激情五月在线 | 亚洲第一香蕉视频 | 在线观看免费 | 久久撸在线视频 | 久久免费视频网站 | 午夜精品999 | 91中文在线 | 91麻豆精品91久久久久同性 | 国产 色| 欧美视频18 | 久久久免费网站 | 欧美天堂影院 | 手机看国产毛片 | 国产成人性色生活片 | 国产精品高潮呻吟久久av无 | 国产精品18久久久久久久网站 | 色先锋资源网 | 久久精品爱爱视频 | 激情五月婷婷综合网 | av在线播放快速免费阴 | 中文在线中文a | 日本夜夜草视频网站 | 在线视频a| 深爱激情五月综合 | 日本高清中文字幕有码在线 | 久久中文网 | 色黄www小说 | 日韩精品资源 | 免费看三级网站 | 狠狠夜夜| 亚洲乱码精品久久久久 | 欧美性生活大片 | 国内精品久久天天躁人人爽 | 日韩激情免费视频 | 精品自拍av| 在线观看视频一区二区 | 中文字幕亚洲精品日韩 | 五月婷婷丁香六月 | 日日干天天操 | 中文区中文字幕免费看 | 欧美精品v国产精品v日韩精品 | 91在线区 | 国产69久久| 欧美中文字幕久久 | 毛片网在线观看 | 中文字幕在线播放一区 | 免费黄色看片 | 一二三久久久 | 国内小视频 | 中文字幕一区二区三区在线观看 | 最新中文字幕在线观看视频 | 久久国产精品一国产精品 | 国产伦精品一区二区三区四区视频 | 国产精品美乳一区二区免费 | 国产精品综合在线观看 | 区一区二在线 | 天天躁日日躁狠狠躁av麻豆 | 亚洲精品高清在线 | 久久久久久久久久久久久国产精品 | 国产 日韩 中文字幕 | 国产无套精品久久久久久 | 激情伊人五月天久久综合 | 日本黄色一级电影 | 日日干 天天干 | 日韩av在线免费播放 | 亚洲精品乱码久久久久久写真 | 精品久久久久久久久亚洲 | 久久免费成人精品视频 | 国产精品久久久久久超碰 | 亚洲视频网站在线观看 | 麻豆91精品视频 | 在线免费日韩 | 97电院网手机版 | 色综合久久久久久中文网 | 香蕉视频免费看 | 玖玖视频精品 | 丁香婷婷激情国产高清秒播 | 亚洲婷婷丁香 | 欧美日韩在线网站 | 国产成人久久精品77777 | 亚洲综合色激情五月 | 狠狠色丁香婷婷综合久久片 | 国产成在线观看免费视频 | 成人av高清在线 | 久久精品一区二区三区四区 | 手机在线黄色网址 | 在线观看视频一区二区 | 91av蜜桃 | 日韩精品在线一区 | 波多野结衣精品视频 | 日韩专区在线 | 成人免费看片98欧美 | av丁香| 天天爽夜夜爽人人爽一区二区 | 久久成人综合 | 亚洲视频1 | 日韩精品一区二区三区在线播放 | 91在线小视频| 香蕉影视| 亚洲国产播放 | 中文字幕影片免费在线观看 | 国产精品久久久av | 高清不卡一区二区三区 | 国产91探花 | 综合网天天 | 日韩啪视频 | 黄色在线视频网址 | 亚洲精品一区二区三区在线观看 | 国产一区二区在线观看视频 | 人成午夜视频 | 久久好看免费视频 | 日本护士三级少妇三级999 | 亚洲国产精品女人久久久 | 麻豆精品传媒视频 | 九九热精 | 国产成人一级电影 | 日韩中文字幕免费视频 | 男女男视频| 国产精品一区二区久久精品爱涩 | 久久99最新地址 | 日韩中文字幕亚洲一区二区va在线 | 久久久久综合精品福利啪啪 | 国产精品久久久久av | 日韩美一区二区三区 | 91成人短视频在线观看 | 久久久精品高清 | 高清一区二区三区av | 免费在线观看污网站 | 久久伦理 | 久久精品小视频 | 久久久国产精华液 | 国产精品麻豆视频 | 在线观看www. | 最新色站 | 国产手机免费视频 | 最近中文字幕第一页 | 久久亚洲欧美日韩精品专区 | 一级做a爱片性色毛片www | 亚洲国产成人久久 | www.伊人网 | 人人插人人草 | 爱情影院aqdy鲁丝片二区 | 天天干夜夜夜操天 | 免费观看的黄色片 | 在线观看日韩免费视频 | 中字幕视频在线永久在线观看免费 | 人人爽人人爽人人片 | 麻豆91视频 | 99视频网站 | 四虎海外影库www4hu | 久久99精品国产一区二区三区 | 伊人成人久久 | 婷婷色中文网 | 国产精品久久久久久欧美 | 久久99精品一区二区三区三区 | 18pao国产成视频永久免费 | 精品久久久久久亚洲综合网 | 欧美日韩精品在线免费观看 | 很黄很污的视频网站 | 精品一区av | 国产日韩欧美在线看 | 亚洲性少妇性猛交wwww乱大交 | 99久久日韩精品视频免费在线观看 | 最近免费观看的电影完整版 | 成 人 黄 色 免费播放 | 日韩在线视 | 亚洲成人精品久久 | 色99久久 | 免费午夜视频在线观看 | 色多多在线观看 |