摘要:JUL(Java util logging),Java原生日志框架,不需要引入第三方依赖包,使用简单方便。

本文分享自华为云社区《Java 日志框架 JUL 详解大全》,作者: 陈皮的JavaLib 。

JUL 简介

JUL(Java util logging),Java 原生日志框架,不需要引入第三方依赖包,使用简单方便,一般在小型应用中使用,主流项目中现在很少使用了。

JUL 架构

  • Application:Java 应用程序。
  • Logger:记录器,Java 应用程序通过调用记录器的方法来发布日志记录。
  • Handler:处理器,每一个 Logger 都可以关联一个或多个 Handler,Handler 从 Logger 获取日志并将日志输出到某个目的地,目的地可以是控制台,本地文件,或网络日志服务,或将它们转发到操作系统日志等等。通过Handler.setLevel(level.off)方法可以禁用一个 Handler,也可以设置其他级别来开启此 Handler。
  • Filter:过滤器,根据条件过滤哪些日志记录。每一个 Logger 和 Handler 都可以关联一个 Filter。
  • Formatter :格式化器,负责对日志事件中的日志记录进行转换和格式化。
  • Level:每一条日志记录都有一个关联的日志级别,表示此条日志的重要性和紧急程度。也可以对 Logger 和 Handler 设置关联的日志级别。

入门示例

package com.chenpi;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Test; /**
* @author 陈皮
* @version 1.0
* @description
* @date 2022/3/2
*/
public class ChenPiJULMain { // JUL 日志框架演示
@Test
public void testLog() { // 获取日志记录器对象
Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain"); // 日志记录输出
logger.severe(">>>>> Hello ChenPi!!");
logger.warning(">>>>> Hello ChenPi!!");
logger.info(">>>>> Hello ChenPi!!"); // 默认日志级别
logger.config(">>>>> Hello ChenPi!!");
logger.fine(">>>>> Hello ChenPi!!");
logger.finer(">>>>> Hello ChenPi!!");
logger.finest(">>>>> Hello ChenPi!!"); // 输出指定日志级别的日志记录
logger.log(Level.WARNING, ">>>>> Hello Warning ChenPi!!"); // 占位符形式
String name = "ChenPi";
int age = 18;
logger.log(Level.INFO, ">>>>> Hello {0},{1} years old!", new Object[]{name, age}); // 异常堆栈信息
logger.log(Level.SEVERE, ">>>>> Hello NPE!", new NullPointerException()); }
}

JUL 默认将日志信息输出到控制台,默认日志级别是 info。控制台输出结果如下:

Logger 父子继承关系

在 JUL 中,Logger 有父子继承关系概念,会根据 Logger 对象的名称的包含关系,划分父子继承关系。对于某个 Logger 对象,如果找不到显性创建的父 Logger 对象,那么它的父 Logger 是根 Logger,即 RootLogger。

子代 Logger 对象会继承父 Logger 的配置,例如日志级别,关联的 Handler,日志格式等等。对于任何 Logger 对象,如果没对其做特殊配置,那么它最终都会继承 RootLogger 的配置。

package com.chenpi;

import java.util.logging.Logger;
import org.junit.Test; /**
* @author 陈皮
* @version 1.0
* @description
* @date 2022/3/2
*/
public class ChenPiJULMain { @Test
public void testLog() { // logger1的父Logger是RootLogger
Logger logger1 = Logger.getLogger("com.chenpi.a");
// logger2的父Logger是logger1
Logger logger2 = Logger.getLogger("com.chenpi.a.b.c"); Logger logger1Parent = logger1.getParent();
System.out.println("logger1Parent:" + logger1Parent + ",name:" + logger1Parent.getName()); Logger logger2Parent = logger2.getParent();
System.out.println("logger1:" + logger1 + ",name:" + logger1.getName());
System.out.println("logger2Parent:" + logger2Parent + ",name:" + logger2Parent.getName()); }
} // 输出结果如下
logger1Parent:java.util.logging.LogManager$RootLogger@61e717c2,name:
logger1:java.util.logging.Logger@66cd51c3,name:com.chenpi.a
logger2Parent:java.util.logging.Logger@66cd51c3,name:com.chenpi.a

日志配置

我们可以通过2种方式调整 JUL 默认的日志行为(设置日志级别,日志输出目的地,日志格式等等),一种是通过在程序中硬编码形式(不推荐),另一种是通过单独的配置文件形式。

硬编码日志配置

在 JUL 中,Logger 是有父子继承关系的,所以当我们需要对某一个 Logger 对象进行单独的配置时,需要将它设置为不继承使用父 Logger 的配置。

以下演示名称为com.chenpi.ChenPiJULMain的 Logger 对象单独进行配置,并且关联两个 Handler。

package com.chenpi;

import java.io.IOException;
import java.util.logging.*;
import org.junit.Test; /**
* @author 陈皮
* @version 1.0
* @description
* @date 2022/3/2
*/
public class ChenPiJULMain { @Test
public void testLog() throws IOException { // 获取日志记录器对象
Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain"); // 关闭默认配置,即不使用父Logger的Handlers
logger.setUseParentHandlers(false); // 设置记录器的日志级别为ALL
logger.setLevel(Level.ALL); // 日志记录格式,使用简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter(); // 控制台输出Handler,并且设置日志级别为INFO,日志记录格式
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.INFO);
consoleHandler.setFormatter(simpleFormatter); // 文件输出Handler,并且设置日志级别为FINE,日志记录格式
FileHandler fileHandler = new FileHandler("./jul.log");
fileHandler.setLevel(Level.FINE);
fileHandler.setFormatter(simpleFormatter); // 记录器关联处理器,即此logger对象的日志信息输出到这两个Handler进行处理
logger.addHandler(consoleHandler);
logger.addHandler(fileHandler); // 日志记录输出
logger.severe(">>>>> Hello ChenPi!!");
logger.warning(">>>>> Hello ChenPi!!");
logger.info(">>>>> Hello ChenPi!!");
logger.config(">>>>> Hello ChenPi!!");
logger.fine(">>>>> Hello ChenPi!!");
logger.finer(">>>>> Hello ChenPi!!");
logger.finest(">>>>> Hello ChenPi!!");
}
}

因为 Logger 设置的日志级别是 ALL,即所有级别的日志记录都可以通过。但是 ConsoleHandler 设置的日志级别是 INFO,所以控制台只输出 INFO 级别以上的日志记录。而 FileHandler 设置的日志级别是 FINE,所以日志文件中输出的是 FINE 级别以上的日志记录。

以下是控制台输出的日志结果:

以下是日志文件jul.log中输出的日志结果:

日志配置文件

通过 debug 调试,按以下方法顺序,可以发现,如果我们没有配置 JUL 的配置文件,系统默认从 JDK 的安装目录下的 lib 目录下读取默认的配置文件logging.properties。

getLogger()  -> demandLogger() -> LogManager.getLogManager() -> ensureLogManagerInitialized() -> owner.readPrimordialConfiguration() -> readConfiguration()

以下是 JDK 自带的 JUL 默认日志配置文件内容:

对于配置选项有哪些,其实可以通过相应类的源码找出,例如 Logger 类的源码如下:

FileHandler 类的源码如下:

所以我们可以拷贝默认的配置文件到我们工程的 resources 目录下,自定义修改配置信息。

############################################################
# 全局属性
############################################################ # 顶级RootLogger关联的Handler,多个Handler使用逗号隔开
# 对于其他Logger,如果没有指定自己的Handler,则默认继承此
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # 默认全局日志级别,Logger和Handler都可以设置自己的日志级别来覆盖此级别
.level= ALL
############################################################
# Handler 配置
############################################################ # FileHandler定义
# 日志文件存储位置
java.util.logging.FileHandler.pattern = ./jul%u.log
# 单个文件的最大字节数,0代表不限制
java.util.logging.FileHandler.limit = 50000
# 文件数量上限,多个文件为jul0.log.0,jul0.log.1 ...
java.util.logging.FileHandler.count = 5
# 日志级别
java.util.logging.FileHandler.level = SEVERE
# 日志追加方式
java.util.logging.FileHandler.append = true
# Handler对象采用的字符集
java.util.logging.FileHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter # ConsoleHandler定义
# 日志级别
java.util.logging.ConsoleHandler.level = INFO
# Handler对象采用的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
############################################################
# Logger 配置
############################################################ # 设置名称为com.chenpi.person的Logger对象的日志级别为WARNING
com.chenpi.person.level = WARNING

然后我们应用中加载我们类路径中自定义的配置文件。

package com.chenpi;

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.junit.Test; /**
* @author 陈皮
* @version 1.0
* @description
* @date 2022/3/2
*/
public class ChenPiJULMain { // JUL 日志框架演示
@Test
public void testLog() throws IOException { // 读取配置文件
// 也可以通过使用java.util.logging.config.file系统属性指定文件名
// 例如 java -Djava.util.logging.config.file=myfile
InputStream resourceAsStream = ChenPiJULMain.class.getClassLoader()
.getResourceAsStream("logging.properties");
// 获取LogManager
LogManager logManager = LogManager.getLogManager();
// 记载配置文件
logManager.readConfiguration(resourceAsStream); // 获取日志记录器对象
Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain"); // 日志记录输出
logger.severe(">>>>> Hello ChenPi!!");
logger.warning(">>>>> Hello ChenPi!!");
logger.info(">>>>> Hello ChenPi!!");
logger.config(">>>>> Hello ChenPi!!");
logger.fine(">>>>> Hello ChenPi!!");
logger.finer(">>>>> Hello ChenPi!!");
logger.finest(">>>>> Hello ChenPi!!"); // 获取日志记录器对象
Logger personLogger = Logger.getLogger("com.chenpi.person"); // 日志记录输出
personLogger.severe(">>>>> Hello Person!!");
personLogger.warning(">>>>> Hello Person!!");
personLogger.info(">>>>> Hello Person!!");
personLogger.config(">>>>> Hello Person!!");
personLogger.fine(">>>>> Hello Person!!");
personLogger.finer(">>>>> Hello Person!!");
personLogger.finest(">>>>> Hello Person!!");
}
}

控制台和文件中输出内容如下:

自定义 Logger

我们可以针对某一个 Logger 进行单独的配置,例如日志级别,关联的 Handler 等,而不默认继承父级的。

当然我们也可以为以包名为名称的 Logger 进行配置,这样这个包名下的所有子代 Logger 都能继承此配置。

############################################################
# 全局属性
############################################################ # 顶级RootLogger关联的Handler,多个Handler使用逗号隔开
# 对于其他Logger,如果没有指定自己的Handler,则默认继承此
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # 默认全局日志级别,Logger和Handler都可以设置自己的日志级别来覆盖此级别
.level= ALL
############################################################
# Handler 配置
############################################################ # FileHandler定义
# 日志文件存储位置
java.util.logging.FileHandler.pattern = ./jul%u.log
# 单个文件的最大字节数,0代表不限制
java.util.logging.FileHandler.limit = 50000
# 文件数量上限
java.util.logging.FileHandler.count = 5
# 日志级别
java.util.logging.FileHandler.level = SEVERE
# 日志追加方式
java.util.logging.FileHandler.append = true
# Handler对象采用的字符集
java.util.logging.FileHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter # ConsoleHandler定义
# 日志级别
java.util.logging.ConsoleHandler.level = INFO
# Handler对象采用的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
############################################################
# Logger 配置
############################################################ # 设置名称为com.chenpi.person的Logger对象的日志级别为WARNING
com.chenpi.person.level = WARNING
# 只关联FileHandler
com.chenpi.person.handlers = java.util.logging.FileHandler
# 关闭默认配置
com.chenpi.person.useParentHandlers = false
package com.chenpi; import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.junit.Test; /**
* @author 陈皮
* @version 1.0
* @description
* @date 2022/3/2
*/
public class ChenPiJULMain { // JUL 日志框架演示
@Test
public void testLog() throws IOException { // 读取配置文件
InputStream resourceAsStream = ChenPiJULMain.class.getClassLoader()
.getResourceAsStream("logging.properties");
// 获取LogManager
LogManager logManager = LogManager.getLogManager();
// 记载配置文件
logManager.readConfiguration(resourceAsStream); // 获取日志记录器对象
Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain"); // 日志记录输出
logger.severe(">>>>> Hello ChenPi!!");
logger.warning(">>>>> Hello ChenPi!!");
logger.info(">>>>> Hello ChenPi!!");
logger.config(">>>>> Hello ChenPi!!");
logger.fine(">>>>> Hello ChenPi!!");
logger.finer(">>>>> Hello ChenPi!!");
logger.finest(">>>>> Hello ChenPi!!"); // 获取日志记录器对象
Logger personLogger = Logger.getLogger("com.chenpi.person"); // 日志记录输出
personLogger.severe(">>>>> Hello Person!!");
personLogger.warning(">>>>> Hello Person!!");
personLogger.info(">>>>> Hello Person!!");
personLogger.config(">>>>> Hello Person!!");
personLogger.fine(">>>>> Hello Person!!");
personLogger.finer(">>>>> Hello Person!!");
personLogger.finest(">>>>> Hello Person!!");
}
}

上述例子中,对于名称为com.chenpi.person的 Logger,只将日志输出到文件中,其他 Logger 则会同时输出到控制台和文件中。

自定义日志格式

以SimpleFormatter类源码为例,再到LoggingSupport类源码,发现首先判断我们是否通过java.util.logging.SimpleFormatter.format属性配置了格式,如果没有则使用默认的日志格式。

所以我们可以在配置文件中自定义日志记录的格式。

############################################################
# 全局属性
############################################################ # 顶级RootLogger关联的Handler,多个Handler使用逗号隔开
# 对于其他Logger,如果没有指定自己的Handler,则默认继承此
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # 默认全局日志级别,Logger和Handler都可以设置自己的日志级别来覆盖此级别
.level= ALL
############################################################
# Handler 配置
############################################################ # FileHandler定义
# 日志文件存储位置
java.util.logging.FileHandler.pattern = ./jul%u.log
# 单个文件的最大字节数,0代表不限制
java.util.logging.FileHandler.limit = 1024
# 文件数量上限
java.util.logging.FileHandler.count = 5
# 日志级别
java.util.logging.FileHandler.level = FINE
# 日志追加方式
java.util.logging.FileHandler.append = true
# Handler对象采用的字符集
java.util.logging.FileHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 自定义SimpleFormatter的日志格式
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n # ConsoleHandler定义
# 日志级别
java.util.logging.ConsoleHandler.level = INFO
# Handler对象采用的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 日志格式,使用系统默认的简单格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
############################################################
# Logger 配置
############################################################ # 设置名称为com.chenpi.person的Logger对象的日志级别为WARNING
com.chenpi.person.level = WARNING

这样,所有绑定了 SimpleFormatter 的 Handler 的所有日志记录就使用了自定义的格式。

当然通过源码发现,日志格式类还有其他几个实现,如下所示:

日志过滤器

Filter,日志过滤器,用来对输出的日志记录进行过滤。我们可以根据多个维度进行过滤,例如只输出 message 包含某段文本信息的日志,只输出某个方法中记录的日志,某个级别的日志等等。

Logger 对象将日志信息包装成一个 LogRecord 对象,然后将该对象传给 Handler 进行处理。每一个 Logger 和 Handler 都可以关联一个 Filter。LogRecord 中包含了日志的文本信息、日志生成的时间戳、日志来自于哪个类、日志来自于哪个方法、日志来自于哪个线程等等信息。

Filter 源码如下所示,我们只需要创建一个 Filter 的实现类,重写方法即可。

package java.util.logging;

/**
* A Filter can be used to provide fine grain control over
* what is logged, beyond the control provided by log levels.
* <p>
* Each Logger and each Handler can have a filter associated with it.
* The Logger or Handler will call the isLoggable method to check
* if a given LogRecord should be published. If isLoggable returns
* false, the LogRecord will be discarded.
*
* @since 1.4
*/
@FunctionalInterface
public interface Filter { /**
* Check if a given log record should be published.
* @param record a LogRecord
* @return true if the log record should be published.
*/
public boolean isLoggable(LogRecord record);
}

我们实现一个 Filter ,如果日志消息包含暴力两个字,则不予放行,即不记录此条日志。

package com.chenpi;

import java.util.logging.Filter;
import java.util.logging.LogRecord; /**
* @author 陈皮
* @version 1.0
* @description
* @date 2022/3/2
*/
public class MyLoggerFilter implements Filter { private static final String SENSITIVE_MESSAGE = "暴力"; @Override
public boolean isLoggable(LogRecord record) {
String message = record.getMessage();
return null == message || !message.contains(SENSITIVE_MESSAGE);
}
}

然后我们将此过滤器对象设置绑定到 Logger 中即可。

package com.chenpi;

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.junit.Test; /**
* @author 陈皮
* @version 1.0
* @description
* @date 2022/3/2
*/
public class ChenPiJULMain { // JUL 日志框架演示
@Test
public void testLog() throws IOException { // 读取配置文件
InputStream resourceAsStream = ChenPiJULMain.class.getClassLoader()
.getResourceAsStream("logging.properties");
// 获取LogManager
LogManager logManager = LogManager.getLogManager();
// 记载配置文件
logManager.readConfiguration(resourceAsStream); // 获取日志记录器对象
Logger logger = Logger.getLogger("com.chenpi.ChenPiJULMain");
// Logger关联过滤器
logger.setFilter(new MyLoggerFilter()); // 日志记录输出
logger.info(">>>>> Hello ChenPi!!");
logger.info(">>>>> 暴力小孩!!");
}
} // 输出结果如下
信息: >>>>> Hello ChenPi!! [星期三 三月 02 15:31:06 CST 2022]

点击关注,第一时间了解华为云新鲜技术~

一文详解Java日志框架JUL的更多相关文章

  1. 一文详解 Java 的八大基本类型

    自从Java发布以来,基本数据类型就是Java语言中重要的一部分,本文就来详细介绍下每种基本类型的具体使用方法和限制. 作者 | Jeremy Grifski 译者 | 弯月,责编 | 郭芮 出品 | ...

  2. Java日志框架总结

    1. 前言 从写代码开始,就陆陆续续接触到了许多日志框架,较常用的属于LOG4J,LogBack等.每次自己写项目时,就copy前人的代码或网上的demo.配置log4j.properties或者lo ...

  3. Java日志框架那些事儿

    文章首发于[博客园-陈树义],点击跳转到原文Java日志框架那些事儿. 在项目开发过程中,我们可以通过 debug 查找问题.而在线上环境我们查找问题只能通过打印日志的方式查找问题.因此对于一个项目而 ...

  4. java日志框架系列(4):logback框架xml配置文件语法

    1.xml配置文件语法 由于logback配置文件语法特别灵活,因此无法用DTD或schema进行定义. 1.配置文件基本结构 配置文件基本结构:以<configuration>标签开头, ...

  5. 「跬步千里」详解 Java 内存模型与原子性、可见性、有序性

    文题 "跬步千里" 主要是为了凸显这篇文章的基础性与重要性(狗头),并发编程这块的知识也确实主要围绕着 JMM 和三大性质来展开. 全文脉络如下: 1)为什么要学习并发编程? 2) ...

  6. Java 日志框架终极教程

    概述 对于现代的 Java 应用程序来说,只要被部署到真实的生产环境,其日志的重要性就是不言而喻的,很难想象没有任何日志记录功能的应用程序被运行于生产环境中.日志 API 所能提供的功能是多种多样的, ...

  7. 详解java动态代理机制以及使用场景

    详解java动态代理机制以及使用场景 https://blog.csdn.net/u011784767/article/details/78281384 深入理解java动态代理的实现机制 https ...

  8. 超全详解Java开发环境搭建

    摘自:https://www.cnblogs.com/wangjiming/p/11278577.html 超全详解Java开发环境搭建   在项目产品开发中,开发环境搭建是软件开发的首要阶段,也是必 ...

  9. 一文详解Hexo+Github小白建站

    作者:玩世不恭的Coder时间:2020-03-08说明:本文为原创文章,未经允许不可转载,转载前请联系作者 一文详解Hexo+Github小白建站 前言 GitHub是一个面向开源及私有软件项目的托 ...

  10. 详解java访问修饰符

    详解java访问修饰符 为了防止初学者看到因为专业的术语而感觉晦涩难懂,我接下来尽量用生动比喻的说法来解释!首先第一点,我们来讲讲什么叫修饰符!看看这个名称,想想他的意思.修饰符!修饰符!,就是用来修 ...

随机推荐

  1. 2023华为杯·第二届中国研究生网络安全创新大赛初赛复盘 Writeup

    A_Small_Secret 题目压缩包中有个提示和另一个压缩包On_Zen_with_Buddhism.zip,提示内容如下: 除了base64还有什么编码 MFZWIYLEMFSA==== asd ...

  2. [Jetson Nano]SSH连接Jetson Nano时出现Xlib: extension NV-GLX missing on display localhost:10.0

    解决SSH连接Jetson Nano时遇到的"Xlib: extension "NV-GLX" missing on display 'localhost:10.0'&q ...

  3. 记一次 .NET 某券商论坛系统 卡死分析

    一:背景 1. 讲故事 前几个月有位朋友找到我,说他们的的web程序没有响应了,而且监控发现线程数特别高,内存也特别大,让我帮忙看一下怎么回事,现在回过头来几经波折,回味价值太浓了. 二:程序到底经历 ...

  4. Kubernetes:kube-apiserver 之准入

    kubernetes:kube-apiserver 系列文章: Kubernetes:kube-apiserver 之 scheme(一) Kubernetes:kube-apiserver 之 sc ...

  5. The 2021 ICPC Asia Regionals Online Contest (II) L Euler Function

    思路来源:Zed222 如果一个区间里的数都有这个质数,那么我们就直接利用性质\(\phi(n * p) = \phi(n) * p\),如果没有这个区间中有没有这个质数的,那么就退化到了单点修改,当 ...

  6. APISIX proxy-cache 插件用法

    APISIX 的 proxy-cache 插件可以对上游的查询进行缓存,这样就不需要上游的应用服务自己实现缓存了,或者也能少实现一部分缓存,通用的交给插件来做. 下面的操作都是基于 APISIX 3. ...

  7. list.add()语句作用

    ----该方法用于向集合列表中添加对象 示例  本示例使用List接口的实现类ArrayList初始化一个列表对象,然后调用add方法向该列表中添加数据. public static void mai ...

  8. IDEA提示Cannot resolve method 'getContextPath()'

    一.问题原因: 二.解决方案: 1.打开Project Structure 2.new一个新的Java的project library文件 3.选择tomcat路径下的lib文件夹. 三.完成 可以看 ...

  9. Tainted canvases may not be exported,视频帧截图跨域

    做原生相机拍照的时候遇见的有趣问题,视频流是上传到云服务器的在线链接,赋值到video的src上,然后使用canvas的drawImg方法去截取视频帧做照片,结果canvas报错视频跨域. 解决方案: ...

  10. [洛谷P3959][NOIP2017提高组] 宝藏

    [NOIP2017 提高组] 宝藏 题目描述 参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 \(n\) 个深埋在地下的宝藏屋, 也给出了这 \(n\) 个宝藏屋之间可供开发的 \(m\) 条道路 ...