错误报告包含设备日志、堆栈轨迹和其他诊断信息,可以帮助我们查找和修复应用中的错误。我们可以通过以下几种方式从设备上获取错误报告。
let 不允许在同一个函数或块作用域中重复声明同一个变量,否则会引起语法错误(SyntaxError)。{ let x = 10; let x = 11;}// Uncaught SyntaxError: Identifier 'x' has already been declared在上面的代码中报错,所以,同一个变量名不可以在同一个作用域内重复声明。{ let x = 10; var x = 1;}即使使用 var 去声明也是不可以的,我们知道当使用 let 声明的时候 x 已经是一个死区了,不可以被重复声明了。Tips:注意在 switch 语句中只有一个块级作用域,所以下面这种情况也是会报错的。let x = 1;switch(x) { case 0: let num; break; case 1: let num;//重复声明了 break;}// 报错如果把 case 后面的语句放到块作用域中则不会报错。let x = 1;switch(x) { case 0: {//块 let num; break; } case 1: {//块 let num;//这里就没有关系了,可以正常声明 break; }}上方代码,case 后面的语句 let 变量声明在放到块中,是单独的作用域,所以就不会报错。
默认情况下,错误报告是 ZIP 文件。ZIP 文件名类似 bugreport-BUILD_ID-DATE.zip,它可能会包含多个文件,但最重要的文件是 bugreport-BUILD_ID-DATE.txt。此文件就是错误报告,它包含系统服务 (dumpsys)、错误日志 (dumpstate) 和系统消息日志 (logcat) 的诊断输出。系统消息包括设备抛出错误时的堆栈轨迹,以及从所有应用中使用 Log 类写入的消息。ZIP 文件中有一个 version.txt 元数据文件,其中包含 Android 版本号,而且启用 systrace 后,ZIP 文件中还会包含 systrace.txt 文件。Systrace 工具可以获取并显示应用进程和其他 Android 系统进程的执行时间,从而帮助分析应用的性能。dumpstate 工具会将文件从设备的文件系统复制到 ZIP 文件的 FS 文件夹下,以便我们引用它们。例如,设备中的 /dirA/dirB/fileC 文件会在 ZIP 文件中生成 FS/dirA/dirB/fileC 条目错误报告文件结构如下:
Python 程序中出现语法错误时,会产生 SyntaxError 类型的异常。编写程序 SyntaxError.py:if 2>1 print('2>1 is True') print('2>1 is False')在第 1 行,有一处语法错误,在行尾缺少冒号 :程序输出结果如下: File "SyntaxError.py", line 1 if 2>1 ^SyntaxError: invalid syntax在第 1 行,File “SyntaxError.py”, line 1指明错误在文件 SyntaxError.py 中的第 1 行在第 4 行,产生 SyntaxError 类型的异常
前面的小节我们介绍了调试布局方面的知识,可以有效的帮助我们解决布局显示异常,本小结我们学习如何获取和分析错误报告。
我们在编译时经常会遇到一些报错信息,但是有时候会排查很久才知道原因所在。其实编译时的信息在 Gradle 日志中我们都可以看到。当我们编译时,在 AndroidStudio 的左下角会看到如下一个 Build 按钮:当我们点击 Build 按钮,我们点击这个按钮就会看到具体的日志信息。我们就可以看到具体的错误日志了,具体我们可以搜FAILURE: Build failed with an exception.这句后面接着的就是具体的错误信息,包括出错文件及原因。我们这里看到说图片文件的名称只能是数字和小写字母,这里包含了 A 这个大写字母,所以编译的时候抛出了异常。Tips: 当我们遇到编译报错时,我们可以在 Build 这个窗口中输入 FAILURE: Build failed with an exception. 搜索到这句信息的位置,后面紧接着的就是具体的错误信息。
如果状态码是 4xx,服务器就应该向用户返回出错信息。一般来说,返回的信息是键值对形式的数据,将 error 作为键名,出错信息作为键值即可。比如,在一个提供查询学生信息的 API 中,要求客户端提供正确的 API key(可以理解为输入了正确的用户名和密码)才能访问,如果提供的 API key 不正确,此时服务器应拒绝访问,并返回错误信息。这样,客户端就知道了为何没能查到信息,修改成正确的 API key 即可。{ error: "Invalid API key"}
可选链是取值操作时所用的安全方法,不能使用在赋值操作上。let obj = {};obj?.name = 'imooc'; // Uncaught SyntaxError: Invalid left-hand side in assignment上面的代码第 2 行使用可选链进行赋值操作,控制台会报语法错误,赋值的左侧是无效的。
类似上述提到的如果创建实例失败的情况,系统会出现异常,但是我们并不能准确判断出异常的信息,这时需要通过监听错误事件来获取报错信息,例如:ws.onerror = function(event){ //这里处理错误信息}
2.2.1 Android 包名错误Android 包名要用 . 至少分为两段,并且每段开头必须是小写字母,比如 imooc.project 是对的,但是 imooc.123.project 是错的。2.2.2 出现红色警告错误出现下面的红色警告标志就说明当前页面有错误,这时点击打包按钮没有反应,要先解决掉错误才能点击打包按钮。2.2.3 出现打包校验错误出现下面的警告,我们需要点击截图中的第一个链接,补充绑定自己的手机号等信息。2.2.4 appid 为空打包时报下面的错误,是因为 Manifest.json 文件中没有填写 appid。HBuilderX 会自动帮我们打开 Manifest.json 文件,在 appid 那一行的后面点击重新获取就可以了。
在编码过程中,要特别注意变量不要超出其类型的范围,请查看如下示例:432运行结果:int 类型能够存储的最大值为2147483647-2147483648我们发现 int 类型能存储的最大值加 1 之后,变成了一个负数,这个数其实就是 int 类型能够存放的最小值,这是因为加 1 之后变量超出了 int 类型能够存储的最大值,这就是我们常常说的内存溢出错误。还要特别注意一点,由于 Java 是强类型的,每个变量都有一个类型,只有给定种类的值能够存储到该变量中。例如,不能将浮点型的数字赋值给整型变量:public class IncompatibleTypeError{ public static void main(String[] args) { int age; age = 20.5f; }}源代码在编译阶段就会报错:$ javac IncompatibleTypeError.javaIncompatibleTypeError.java:4: 错误: 不兼容的类型: 从float转换到int可能会有损失 age = 20.5f; ^1 个错误
面试官提问: 上述你提到了 UDP 和 TCP 报文,它们的具体结构是怎样的?题目解析:在上个题目中我们总结了 TCP 协议和 UDP 协议的不同点,其中谈到了 TCP 和 UDP 协议首部格式不同,接下来分别画图分析。 (UDP 报文首部) 如上图可见,UDP 首部只有 8 个字节的数据,包括源端口号、目标端口号、长度以及校验和。源端口号:发送计算机的应用端口;目标端口号:接收端计算机的接收端口,也是占用 16 位 Bit;长度:表示 UDP 报文首部以及携带数据的长度;校验和:校验数据在传输过程中是否损坏。 (TCP 报文首部)在画完了示意图之后,关于 TCP 报文首部,我们需要解释的字段:序号:对字节流编号,例如本次传输的序号是 100,携带的数据长度是 100 字节,那么下次传输的序号就是 200;确认号:客户端 A 往服务器端 B 发送了一个报文,序号是 100,携带的数据长度是 100 字节,那么 B 往 A 发送的报文中确认号就是 200,表示期望收到的下一个报文的序号。标志位 CWR(Congestion Window Reduce):拥塞窗口减少标志;标志位 ECE(ECN Echo):ECE 标志等于 1 时,通知接收方,表示接收方到这边的网络存在拥塞;标志位 URG(Urgent):本报文是否包含紧急数据,只有当 URG=1 时,"校验和" 后面的 "紧急指针" 字段才有效;标志位 ACK(Acknowledgement):ACK=1 则表示前面发送的确认号是否有效,TCP 连接建立之后,ACK 必须设置为 1;标志位 PSH(Push):PSH 设置为 1 则表示需要将收到的数据立即传输给上层应用,否则先放缓存;标志位 RST(Reset):RST 设置为 1 则表示 TCP 连接出现异常,需要强制断开;标志位 SYN(Synchronize):SYN 设置为 1 则表示希望建立连接;标志位 FIN(Finsish):FIN 设置为 1 则表示数据已经发送完成,可以断开 TCP 连接。上述定义中,序号、确认号以及 ACK、SYN 和 FIN 标志位是我们需要重点关注的部分,因为在 TCP 建立连接和断开连接时会涉及到。
ngx_http_limit_req_module 模块主要用于处理突发流量,它基于漏斗算法将突发的流量限定为恒定的流量。如果请求容量没有超出设定的极限,后续的突发请求的响应会变慢,而对于超过容量的请求,则会立即返回 503(默认)错误。该模块模块中比较重要的指令有:limit_req_zone 指令,定义共享内存, key 关键字以及限制速率Syntax: limit_req_zone key zone=name:size rate=rate [sync];Default: —Context: httplimit_req 指令,限制并发连接数Syntax: limit_req zone=name [burst=number] [nodelay | delay=number];Default: —Context: http, server, locationlimit_req_log_level 指令,设置服务拒绝请求发生时打印的日志级别Syntax: limit_req_log_level info | notice | warn | error;Default:limit_req_log_level error;Context: http, server, locationlimit_req_status 指令, 设置服务拒绝请求发生时返回状态码Syntax: limit_req_status code;Default: limit_req_status 503;Context: http, server, location
WeakMap 像 Map 一样可以接受一个二维数组进行初始化。var wm = new WeakMap([ [{name: 'imooc'}, 'imooc'], [{name: 'lesson'}, 'ES6 Wiki']])console.log(wm)上面的代码打印结果如下:从打印的结果可以大概了解 WeakMap 的存储方式,WeakMap 的实例本来就是一个对象。WeakMap 只提供了四个方法用于操作数据。方法名描述 set 接收键值对,向 WeakMap 实例中添加元素 get 传入指定的 key 获取 WeakMap 实例上的值 has 传入指定的 key 查找在 WeakMap 实例中是否存在 delete 传入指定的 key 删除 WeakMap 实例中对应的值看如下实例:var wm1 = new WeakMap();var wm2 = new WeakMap();var wm3 = new WeakMap();var o1 = {name: 'imooc'};var o2 = function(){};var o3 = window;// 使用 set 方法添加元素,value 可以是任意值,包括对象、函数甚至另外一个WeakMap对象wm1.set(o1, 'ES6 Wiki');wm1.set(o2, 10);wm2.set(o1, o2);wm2.set(o3, null);wm2.set(wm1, wm2);wm1.get(o2); // 10wm2.get(o2); // undefined,wm2 中没有 o2 这个键wm2.get(o3); // nullwm1.has(o2); // truewm2.has(o2); // falsewm2.has(o3); // true (即使值是null)wm3.set(o1, 'lesson is ES6 Wiki!');wm3.get(o1); // lesson is ES6 Wiki!wm1.has(o1); // truewm1.delete(o1);wm1.has(o1); // false上面的实例基本涵盖了 WeakMap 四种方法的基本使用情况,上面也提到了 WeakMap 的 key 只能是对象类型的,如果 WeakMap 的 key 是基本类型数据时就会报错。var wm = new WeakMap();wm.set('lesson', 'ES6 Wiki');// Uncaught TypeError: Invalid错误value used as weak map key上面代码中在设置 wm 值时,报错了。从报错类型知道是一个类型错误,弱引用映射的键是无效的。
Http 协议请求报文的本质就是一堆字符串,只是这堆字符是有格式的,发送方跟接收方都需要按照这个格式来拼接和拆解内容。我们要实现一个 Web 服务,了解这个是最基本的要素。以下截图的报文是通过 tcpflow(一款功能强大的、基于命令行的免费开源工具)在 Linux 系统抓包获取的。sudo tcpflow -c port 8080
Maven 的默认配置中,会在 target\surefire-reports 目录下生成测试报告。我们执行 mvn clean test,就可以观察到该目录生成。我们可以在 txt 格式的文档中看到生成的测试报告。这里的测试报告基本上和控制台输出的内容是类似的。大家可能也注意到了,我们在执行测试用例的时候,同时生成了两种类型的文件,一种是 txt 格式,另一个则是 XML 格式。txt 格式: 为了让执行测试用例的开发者更加直观的看到测试用例的运行结果;XML格式: 更多的是为了支持其他工具的解析。
select count(*),gender,avg(age) as avg_age from user group by gender where avg_age > 20;select count(*),gender,avg(age) as avg_age from user group by gender having avg_age > 20;我们的目的是 "按照性别进行分组,统计平均年龄大于 20 的人数、性别和平均年龄"。但是第一条 sql 执行会报错:ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where avg_age > 20' at line 1,即对于 where 语句,全表并没有 avg_age 平均年龄这个字段,所以会搜索失败。从上述的实验结果总结来看,where 和 having 的核心区别有三点:① 使用范围的不同:where:能够用于 select、update、delete 语句;having:只能用于 select 查询语句。② 执行顺序不同:where 修饰的搜索条件是在分组完成之前执行;having 修饰的搜索条件是在分组完成之后执行。如果一条 sql 语句同时包含 where 和 having 关键词,会先执行 where 搜索,再执行 having 搜索条件。③ 聚合函数的联合使用:where:不能联合聚合函数(sum、avg、count、max、min 这类)使用;having:能够联合聚合函数使用。
启动监控服务端和客户端,然后关闭客户端,稍等一会检查指定的报警接收邮箱,就会发现已收到报警邮件了。项目实例离线邮件报警内容
Http 定义了 5大类别的错误码,这些错误码是通用的,其中只有 5XX 是表示后台服务的错误。各个系统的后端服务的用途/业务相差甚远,为数不多 5XX 远远不够用来表示可能出现的各种情况。于是,后端系统需要根据自己的业务制定业务级别的错误码,而 Http 的错误码,我们称其为协议级别的错误码。
如需直接从我们的设备上获取错误报告,请执行以下操作:确保已启用开发者选项;在开发者选项中,点按错误报告;选择所需的错误报告类型,然后点按报告;稍后,我们便会收到错误报告已准备就绪的通知。要分享错误报告,请点按该通知。
如果没有命中查询缓存,接下来就要进入解析器阶段了。解析器负责词法解析和语法解析。首先是词法解析,MySQL 需要识别所输入的字符串分别代表什么,它会从左到右一个字符、一个字符地输入,然后根据构词规则识别单词。select id from a where id=1;关键字非关键字关键字非关键字关键字非关键字select字段idfrom表名awhere字段id等于1接下来是语法解析,判断输入的这个SQL语句是否符合MySQL语法规则。如果语法不对,会收到错误信息提示:“ERROR 1064 (42000): You have an error in your SQL syntax;”。如下面这个SQL语句的where少了一个e。root@localhost [tempdb]>select id from a wher id=1;ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'id=1' at line 1
对应 var 我们知道可以变量提升的,提升到作用域的最顶部,作用域是全局,使得声明变量前也可以使用,但值为 undefined。{ console.log(bar); // 输出undefined,没有值但不会报错 var bar = 1;}一般变量都应该先声明再使用,所以 let 和 const 语法规定必须声明后使用,否则报错。{ console.log(name); // 引用错误 ReferenceError: name is not defined. let name = 'imooc';}上面代码中,都是在声明前的时候使用变量的,这时候由于 let 不能进行变量提升所以会报引用错误。
当获取节点的方法没有匹配到任何元素的时候,是可能返回 null 或者 空集合的。var el = document.querySelector('#dfsafds');var elList = document.querySelectorAll('.dfsafds');el.innerHTML = '<p>我写的代码从来不会报错!</p>';elList[1].innerHTML = '<p>我写的代码从来不会报错!</p>';// Cannot set property 'innerHTML' of null碰到这种情况,上述代码就报错了,假如后面代码存在渲染逻辑,则不会再继续执行,最后换来一份 辞退报告。所以在没有把握的情况下一定要进行空判断。var el = document.querySelector('#dfsafds');if (el) { el.innerHTML = '<p>我写的代码从来不会报错!</p>'; } else { console.log('节点还没渲染出来');}或者使用 try ... catch ... 。var el = document.querySelector('#dfsafds');try { el.innerHTML = '<p>我写的代码从来不会报错!</p>';} catch (err) { console.error(err); console.log('节点还没渲染出来');}
当父类中方法不希望被重写时,可以将该方法标记为 final:class SuperClass { public final void finalMethod() { System.out.println("我是final方法"); }}class SubClass extneds SuperClass { // 被父类标记为final的方法不允许被继承,编译会报错 @Override public void finalMethod() { }}编辑执行,将会报错:SubClass.java:4: 错误: SubClass中的finalMethod()无法覆盖SuperClass中的finalMethod() public void finalMethod() { ^ 被覆盖的方法为final1 个错误
可视化监控提供了全面了解项目运行状况的手段,但是我们不可能 7*24 小时盯着界面看哪个应用离线了。最妙的效果是,项目离线时自动通知售后、运维等相关技术人员。Spring Boot Admin 也提供了自动报警的功能,简直太完美了,接下来我们来实现下。
本节课程我们主要学习了如何获取和分析错误报告。本节课程的重点如下:掌握如何获取错误报告;掌握如何分析常见日志。
文件管理程序提供了错误处理功能,如果执行某条命令时发生了错误,例如文件不存在,仅仅终止该命令,而不是终止程序。> cat non-exist-file[Errno 2] No such file or directory: 'non-exisit-file'>在第 1 行,打印文件 non-exist-file,该文件并不存在,cat 命令运行会出错在第 2 行,cat 命令执行中止,打印错误提示信息在第 3 行,cat 命令中止后,打印命令提示符,等待用户输入新的命令
如果我们只连接了一台设备,则可以使用 adb 获取错误报告,如下所示:$ adb bugreport D:\ReportsTips:如果没有指定错误报告的路径,则系统会将其保存到本地目录。如果我们连接了多台设备,则必须使用 -s 选项指定设备。运行以下 adb 命令可获取设备序列号并生成错误报告:$ adb devicesList of devices attachedemulator-5554 device8XV7N15C31003476 device$ adb -s 8XV7N15C31003476 bugreport
但是有个问题,当程序需要同时处理多个异步任务时,那我们使用 async/await 怎样捕获那个异步任务出现错误呢?try 块中的代码只要程序出现错误就会抛出错误,但是不知道是哪个异步任务出错了不利于定位问题。如果使用多个 try...catch :const task = function (num) { return new Promise((resolve, reject) => { setTimeout(() => { if (num === 300) { reject('throw error') } else { resolve('imooc'); } }, 1000) })}async function foo() { try { let res1 = await task(100); try { let res2 = await task(200); try { let res3 = await task(300); } catch(e) { console.log('res3', e) } } catch(e) { console.log('res2', e) } } catch(e) { console.log('res1', e) }}foo() // res3 throw error看到上面的代码你是不是觉得很难受啊,又回到了嵌套地狱的原始问题了。async 函数在异常捕获时,没有非常完美的解决方案,这主要源自依赖 try...catch 对错误的捕获。但有一些还算比较优雅的解决方案,我们已经知道了 async 函数返回的是一个 Promise 那么我们是不是可以使用 Promise 的 catch 来捕获呢?答案是当然的呢。async function foo() { let res1 = await task(100).catch(err => console.log('res1', err)); let res2 = await task(200).catch(err => console.log('res2', err)); let res3 = await task(300).catch(err => console.log('res3', err));}foo() // res3 throw error上面的代码看起来就比嵌套的 try...catch 感觉好很多,这也是一个比较好的解决方式。在使用 catch 时需要弄清楚 Promise 和 async 函数之间的关系,不然就很难理解这种写法。
如果你在 Postman 上看到一个更新失败的通知,你可以使用 DevTools 来检查是什么错误。点击 View > Developer > Show DevTools 来打开 DevTools。当大家打开 DevTools 会看到一个熟悉的画面,所有的错误和警告都会显示在这里:一些已知的错误如下:错误消息: Cannot update while running on a read-only volume:这个错误说明用户在 Postman 安装的目录中没有写权限。要解决这个问题,将Postman 移到用户有写权限的目录,例如 Mac 的 /Application 目录,Linux 的 home 目录。错误消息: Code signature at URL file:///… did not pass validation: code object is not signed at all:这个错误说明同时有多个更新在运行,在应用程序上次更新完成之前被打开,就会发生这种情况。要解决这个问题,请退出并重新打开应用程序。