主流的APM监控厂商,当前都提供了浏览器端的监控,涉及到浏览器端的监控,就是涉及到浏览器端的监控agent的嵌入。浏览器端监控方式常规情况下都是在输出的html页面中加入 浏览器 agent,其本质上就是一个js文件或者一段js代码。如果使用人工的方式进行嵌入对于存量项目工作量可能会很大,并且一些存量的项目如果不能修改代码也是没有办法做到人工的嵌入,这里就涉及到agent自动嵌入。

当前有如下几种嵌入方式:

Javaagent嵌码方式

Java web 开发很多都是 jsp 输出的、jsp 最终会转换为 Servlet ,只需要对 Servlet 的 html 输出进行前面尽可。

public final class test1_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent
{
private static java.util.Vector _jspx_dependants;
public java.util.List getDependants() {
return _jspx_dependants;
}
//用于响应用户的方法
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.IOException, ServletException
{ //built-in objects(variavles) are created here.
//获得页面输出流
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
//获得页面输出流
JspWriter out = null; //not PrintWriter. JspWriter is buffered defautly.
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
//开始生成响应
try
{
_jspxFactory = JspFactory.getDefaultFactory();
//设置输出的页面格式
response.setContentType("text/html; charset=gb2312");
pageContext = _jspxFactory.getPageContext(this, request,
response, null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
//页面输出流
out = pageContext.getOut();
_jspx_out = out;
//输出流,开始输出页面文档
out.write("rn");
//下面输出HTML标签
out.write("rn");
out.write("rn");
out.write("
rn"); 
 
out.write("
first Jsprn"); 
 
out.write("rn");
out.write("
rn"); 
 
out.write("rn");
out.write("rn");
out.write("rn");
out.write("rn");
}
catch (Throwable t)
{
if (!(t instanceof SkipPageException))
{
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null) _jspx_page_context.handle
PageException(t);
}
}
finally
{
if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_
page_context);
}
}
}

以上是一个JSP转换为 Servlet 内容,其实只要在输出

的地方进行修改就可以

out.write("

rn");


以上的代码的嵌入不能粗暴的直接在 out 对应 JspWriter 类的 write 代码进行嵌码,因为那样影响的范围就会非常的大。

嵌码逻辑首先确定到类,是 org.apache.jasper.runtime.HttpJspBase的子类

分析 _jspService 方法字节码,注意其有3个连续指令 1)LDC 文指令,并且判断静态字符中包含 内容。 2)获取 out 对象 3)调用write方法 如果连续的3个指令满足此方法,修改静态的字符串嵌入agen代码。

以上的方式嵌入agent的时候,同时也可以嵌入后端的信息 traceid 等,可以做到前后端关联。优点:可以做到前后端关联,依赖小监控的应用都可以做到自动的关联。

缺点: 1)当前很多的应用已经不再使用JSP输出,使用其他的模板工具等无法进行嵌入,适配的范围还是有限。 2) 如果有些特定的字符输出,比如没有字节使用字符串,而是使用了引用了一个变量来输出 字符串,也是无法识别的。 3) 如果输出的 html 固定长度 指定了 content-length ,在嵌入内容的时候使 html 变长,但是并没有修改content-length,从而会导致浏览器读取的html内容缺失,危险性较高。

Javaagent 拦截字节流嵌入

Javaagent 不在输出文本内容的地方进行嵌码,而是在输出html字节流的地方拦截字节流,判断输出的字节流中包含

输出内容,直接在字节流中追加 agent 内容。这里不同的容器需要修改的位置不一样。

优点:容器使用任何的方式输入的内容都可以识别嵌码,识别的范围广,

缺点: 1)需要过滤输出的字节,判断是否有特定的字符,资源消耗增加。 2)进行字节匹配的时候,没有办法知道字符串编码的格式,默认使用UTF-8,如果是特殊的输出需要用户指定。 3)仅仅通过字节的校验也有可能出错,比如在 html 注释出现 也可能会被嵌码,所以危险性较高。

Nginx + Javaagent 方式自动嵌入

Nginx方式自动嵌入式依赖的Nginx的自动修改response html 内容的能力,. 通过

location / {

sub_filter '' ''; -- 把 标签替换为带有agent的标签

}

仅仅是通过 nginx 进行 html 修改是没有办法做到前后端的关联的,比如这个请求的后端生成的 traceid,属于哪个事务,属于哪个应用等情况。

Javaagent 进行嵌码完成,其拦截输出,在输出的时候在 cookie 中增加上后端的一些的信息。这些信息前端的js agent 可以同cookie中进行获取从而进行前后端的数据关联。优点:使用 nginx 进行嵌码相对安全,nginx 在7层上的处理技术更加的完善。

缺点: